Pass custom data to service worker sync?

时光总嘲笑我的痴心妄想 提交于 2020-06-26 14:05:14

问题


I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.

But is there a way to pass the POST data to the service worker, so it makes the same request again?

Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.

Any more elegant way?

PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(


回答1:


You can use fetch-sync

or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.

  1. first of all, i send the message from html.

    
    // send message to serviceWorker
    function sync (url, options) {
      navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
    }
    
  2. i got this message in serviceworker, and then i store it.

    
    const syncStore = {}
    self.addEventListener('message', event => {
      if(event.data.type === 'sync') {
        // get a unique id to save the data
        const id = uuid()
        syncStore[id] = event.data
        // register a sync and pass the id as tag for it to get the data
        self.registration.sync.register(id)
      }
      console.log(event.data)
    })
    
  3. in the sync event, i got the data and fetch

    
    self.addEventListener('sync', event => {
      // get the data by tag
      const {url, options} = syncStore[event.tag]
      event.waitUntil(fetch(url, options))
    })
    

it works well in my test, what's more you can delete the memory store after the fetch

what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.

  1. as now i have to communicate between each other, i will change the fucnction sync into this way

    
    // use messagechannel to communicate
    sendMessageToSw (msg) {
      return new Promise((resolve, reject) => {
        // Create a Message Channel
        const msg_chan = new MessageChannel()
    
    
    // Handler for recieving message reply from service worker
    msg_chan.port1.onmessage = event => {
      if(event.data.error) {
        reject(event.data.error)
      } else {
        resolve(event.data)
      }
    }
    
    navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
    
    }) } // send message to serviceWorker // you can see that i add a parse argument // this is use to tell the serviceworker how to parse our data function sync (url, options, parse) { return sendMessageToSw({type: 'sync', url, options, parse}) }
  2. i also have to change the message event, so that i can pass the port to sync event

    
    self.addEventListener('message', event => {
      if(isObject(event.data)) {
        if(event.data.type === 'sync') {
          // in this way, you can decide your tag
          const id = event.data.id || uuid()
          // pass the port into the memory stor
          syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
          self.registration.sync.register(id)
        }
      }
    })
    
  3. up to now, we can handle the sync event

    
    self.addEventListener('sync', event => {
      const {url, options, port, parse} = syncStore[event.tag] || {}
      // delete the memory
      delete syncStore[event.tag]
      event.waitUntil(fetch(url, options)
        .then(response => {
          // clone response because it will fail to parse if it parse again
          const copy = response.clone()
          if(response.ok) {
            // parse it as you like
            copy[parse]()
            .then(data => {
              // when success postmessage back
              port.postMessage(data)
            })
          } else {
            port.postMessage({error: response.status})
          }
        })
        .catch(error => {
          port.postMessage({error: error.message})
        })
      )
    })
    

At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.

As you have mention that, you may want to open the window. i advice that you can use serviceworker to send a notification.

self.addEventListener('push', function (event) {

  const title = 'i am a fucking test'
  const options = {
    body: 'Yay it works.',
  }
  event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {

  event.notification.close()

  event.waitUntil(
    clients.openWindow('https://yoursite.com')
  )
})

when the client click we can open the window.




回答2:


To comunicate with the serviceworker I use a trick:

in the fetch eventlistener I put this:

self.addEventListener('fetch', event => {
  if (event.request.url.includes("sw_messages.js")) {
    var zib = "some data";
    event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
      headers: {
        'Content-Type': 'application/javascript'
      }
    }));
  }

  return;
});

then, in the main html I just add:

<script src="sw_messages.js"></script>

as the page loads, global variable msg will contain (in this example) "some data".



来源:https://stackoverflow.com/questions/41798009/pass-custom-data-to-service-worker-sync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!