服务工作者在浏览器离线时保存表单数据

伊冯娜·阿布罗

我是Service Worker的新手,并且浏览了各种文档(GoogleMozillaserviceworke.rsGithubStackOverflow问题)。最有用的是ServiceWorkers食谱

大多数文档似乎都指向缓存整个页面,以便该应用程序完全脱机工作,或者将用户重定向到脱机页面,直到浏览器可以重定向到Internet。

但是,我想做的是将表单数据存储在本地,以便我的Web应用程序可以在用户连接恢复后将其上传到服务器。我应该使用哪个“食谱”?我认为这是Request Deferrer我是否需要其他任何东西来确保Request Deferrer可以正常工作(除了我的网页中的Service Worker检测器脚本之外)?任何提示和技巧大加赞赏。

控制台错误

Request Deferrer配方和代码似乎不单独工作,因为它不包含文件缓存。我为服务工作者库文件添加了一些缓存,但是当我离线时提交表单时,仍然出现此错误:

Console: {"lineNumber":0,"message":
"The FetchEvent for [the form URL] resulted in a network error response: 
the promise was rejected.","message_level":2,"sourceIdentifier":1,"sourceURL":""}

我的服务人员

/* eslint-env es6 */
/* eslint no-unused-vars: 0 */
/* global importScripts, ServiceWorkerWare, localforage */
importScripts('/js/lib/ServiceWorkerWare.js');
importScripts('/js/lib/localforage.js');

//Determine the root for the routes. I.e, if the Service Worker URL is http://example.com/path/to/sw.js, then the root is http://example.com/path/to/


var root = (function() {
  var tokens = (self.location + '').split('/');
  tokens[tokens.length - 1] = '';
  return tokens.join('/');
})();

//By using Mozilla’s ServiceWorkerWare we can quickly setup some routes for a virtual server. It is convenient you review the virtual server recipe before seeing this.


var worker = new ServiceWorkerWare();

//So here is the idea. We will check if we are online or not. In case we are not online, enqueue the request and provide a fake response. 
//Else, flush the queue and let the new request to reach the network.


//This function factory does exactly that.


function tryOrFallback(fakeResponse) {

//Return a handler that…


  return function(req, res) {

//If offline, enqueue and answer with the fake response.


    if (!navigator.onLine) {
      console.log('No network availability, enqueuing');
      return enqueue(req).then(function() {

//As the fake response will be reused but Response objects are one use only, we need to clone it each time we use it.


        return fakeResponse.clone();
});
}

//If online, flush the queue and answer from network.


    console.log('Network available! Flushing queue.');
    return flushQueue().then(function() {
      return fetch(req);
});
};
}

//A fake response with a joke for when there is no connection. A real implementation could have cached the last collection of updates and keep a local model. For simplicity, not implemented here.


worker.get(root + 'api/updates?*', tryOrFallback(new Response(
  JSON.stringify([{
    text: 'You are offline.',
    author: 'Oxford Brookes University',
    id: 1,
    isSticky: true
}]),
  { headers: { 'Content-Type': 'application/json' } }
)));

//For deletion, let’s simulate that all went OK. Notice we are omitting the body of the response. Trying to add a body with a 204, deleted, as status throws an error.


worker.delete(root + 'api/updates/:id?*', tryOrFallback(new Response({
    status: 204
})));

//Creation is another story. We can not reach the server so we can not get the id for the new updates. 
//No problem, just say we accept the creation and we will process it later, as soon as we recover connectivity.


worker.post(root + 'api/updates?*', tryOrFallback(new Response(null, {
    status: 202
})));

//Start the service worker.


worker.init();

//By using Mozilla’s localforage db wrapper, we can count on a fast setup for a versatile key-value database. We use it to store queue of deferred requests.


//Enqueue consists of adding a request to the list. Due to the limitations of IndexedDB, Request and Response objects can not be saved so we need an alternative representations. 
//This is why we call to serialize().`


function enqueue(request) {
  return serialize(request).then(function(serialized) {
    localforage.getItem('queue').then(function(queue) {
      /* eslint no-param-reassign: 0 */
      queue = queue || [];
      queue.push(serialized);
      return localforage.setItem('queue', queue).then(function() {
        console.log(serialized.method, serialized.url, 'enqueued!');
      });
    });
  });
}

//Flush is a little more complicated. It consists of getting the elements of the queue in order and sending each one, keeping track of not yet sent request. 
//Before sending a request we need to recreate it from the alternative representation stored in IndexedDB.


function flushQueue() {

//Get the queue


  return localforage.getItem('queue').then(function(queue) {
    /* eslint no-param-reassign: 0 */
    queue = queue || [];

//If empty, nothing to do!


    if (!queue.length) {
      return Promise.resolve();
    }

//Else, send the requests in order…


    console.log('Sending ', queue.length, ' requests...');
    return sendInOrder(queue).then(function() {

        //Requires error handling. Actually, this is assuming all the requests in queue are a success when reaching the Network. 
        //    So it should empty the queue step by step, only popping from the queue if the request completes with success.


      return localforage.setItem('queue', []);
    });
  });
}

//Send the requests inside the queue in order. Waiting for the current before sending the next one.


function sendInOrder(requests) {

//The reduce() chains one promise per serialized request, not allowing to progress to the next one until completing the current.


  var sending = requests.reduce(function(prevPromise, serialized) {
    console.log('Sending', serialized.method, serialized.url);
    return prevPromise.then(function() {
      return deserialize(serialized).then(function(request) {
        return fetch(request);
      });
    });
  }, Promise.resolve());
  return sending;
}

//Serialize is a little bit convolved due to headers is not a simple object.


function serialize(request) {
  var headers = {};

//for(... of ...) is ES6 notation but current browsers supporting SW, support this notation as well and this is the only way of retrieving all the headers.


  for (var entry of request.headers.entries()) {
    headers[entry[0]] = entry[1];
  }
  var serialized = {
    url: request.url,
    headers: headers,
    method: request.method,
    mode: request.mode,
    credentials: request.credentials,
    cache: request.cache,
    redirect: request.redirect,
    referrer: request.referrer
  };

//Only if method is not GET or HEAD is the request allowed to have body.


  if (request.method !== 'GET' && request.method !== 'HEAD') {
    return request.clone().text().then(function(body) {
      serialized.body = body;
      return Promise.resolve(serialized);
    });
  }
  return Promise.resolve(serialized);
}

//Compared, deserialize is pretty simple.


function deserialize(data) {
  return Promise.resolve(new Request(data.url, data));
}

var CACHE = 'cache-only';

// On install, cache some resources.
self.addEventListener('install', function(evt) {
    console.log('The service worker is being installed.');

    // Ask the service worker to keep installing until the returning promise
    // resolves.
    evt.waitUntil(precache());
});

// On fetch, use cache only strategy.
self.addEventListener('fetch', function(evt) {
    console.log('The service worker is serving the asset.');
    evt.respondWith(fromCache(evt.request));
});

// Open a cache and use `addAll()` with an array of assets to add all of them
// to the cache. Return a promise resolving when all the assets are added.
function precache() {
    return caches.open(CACHE).then(function (cache) {
        return cache.addAll([
          '/js/lib/ServiceWorkerWare.js',
          '/js/lib/localforage.js',
          '/js/settings.js'
        ]);
    });
}

// Open the cache where the assets were stored and search for the requested
// resource. Notice that in case of no matching, the promise still resolves
// but it does with `undefined` as value.
function fromCache(request) {
    return caches.open(CACHE).then(function (cache) {
        return cache.match(request).then(function (matching) {
            return matching || Promise.reject('no-match');
        });
    });
}

这是我离线时在Chrome中收到的错误消息:

Chrome中的服务人员错误

(在Firefox中发生了类似的错误-它位于的第409行ServiceWorkerWare.js

   ServiceWorkerWare.prototype.executeMiddleware = function (middleware, 
request) {
        var response = this.runMiddleware(middleware, 0, request, null);
        response.catch(function (error) { console.error(error); });
        return response;
    };
伊冯娜·阿布罗

服务工作者倾向于缓存静态HTML,CSS,JavaScript和图像文件。

我需要使用PouchDB并将其与CouchDB同步

为什么选择CouchDB?

  • CouchDB是一个NoSQL数据库,由许多使用JSON创建的文档组成。
  • 它具有版本控制(每个文档的_rev属性具有最后修改日期)
  • 它可以与本地JavaScript应用程序PouchDB同步,该应用程序通过使用IndexedDB的浏览器将数据存储在本地存储中。这使我们可以创建离线应用程序。
  • 这两个数据库都是数据的“主”副本。

PouchDB是CouchDB的本地JavaScript实现。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

什么是服务工作者控制台?&Chrome浏览器在哪里?

刷新时激活更新的服务工作者

如何卸载服务工作者?

支持网络工作者的浏览器版本?

“服务工作者”是否支持离线HTML表单数据?

离线时服务工作者未注册

如何列出服务工作者已经存储在浏览器缓存中的博客帖子?

服务工作者不在离线工作

通过http的服务工作者:获取脚本时发生SSL证书错误

如何使用新部署的站点版本(Firebase)从用户的浏览器中删除在先前部署中注册的服务工作者?

服务工作者未在实时(https)服务器上加载

使用CSRF令牌缓存请求时,如何减少服务工作者缓存的大小

notificationclick事件服务工作者

将服务工作者保存在缓存中有意义吗?

用户在网站上时,可以使用服务工作者显示通知吗?

注销时如何清除服务工作者缓存?

从Create React App注册服务工作者时如何更新页面

PWA-服务工作者(反应)如何工作?

使用Workbox CLI及其服务工作者生成器的PWA无法离线工作

Google Chrome浏览器保存的表单数据

服务工作者中的XMLHttpRequest

在服务工作者中添加图像和CSS文件以及HTML文件以进行离线缓存

服务工作者缓存文件但不离线加载

服务工作者,它是如何工作的?

使用 PWA 服务工作者在离线模式下保存/提供 Web 服务

Netlify 表单和服务工作者

无法订阅通知错误:此浏览器已禁用或不支持服务工作者

访问任何站点的服务工作者上下文/世界/执行环境以通过浏览器插件代理 self.registration.showNotification?

服务工作者缓存与 VuexPersistence