🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

ServiceWorkerとのやり取りにMessageChannelを使う

何を今さらって感じですが・・。

ぱっと思い出せなくて調べたので、忘れないようにメモっとく。

Browser → ServiceWorker

これは割と簡単で、`navigator.serviceWorker.controller`に生えてる`postMessage()`を使えばいい。

// Browser
(async function main() {
  // もちろん前もって登録しておいて
  await navigator.serviceWorker.register('./sw.js');

  const ctrl = navigator.serviceWorker.controller;
  ctrl.postMessage({ type: 'data', payload: { x: 1 }});
}());

こうやって送って、受け取り側はこう。

// ServiceWorker
self.addEventListener('message', ev => {
  const { type, payload } = ev.data;
  console.log(type, payload);
});

そしてふと思う、逆はどうやるんや・・?

ServiceWorker → Browser

ここに正解があった。

samples/service-worker/post-message at gh-pages · GoogleChrome/samples · GitHub

`MessageChannel`を使ってやり取りする。

あえてServiceWorker側に状態を定義して、簡単なカウンターを作ってみる例。

// Browser
(async function main() {
  await navigator.serviceWorker.register('./sw.js');

  const { $incBtn, $decBtn, $span } = initView();
  const ctrl = navigator.serviceWorker.controller;
  const { port1, port2 } = new MessageChannel();

  // handle messages from service worker
  // addEventListener() does not work...?
  port1.onmessage = ev => {
    $span.textContent = ev.data;
  };

  // init service worker with `MessagePort`
  ctrl.postMessage({ type: 'init' }, [ port2 ]);

  // now, we can interact with service worker!
  $incBtn.onclick = () => ctrl.postMessage({ type: '++' });
  $decBtn.onclick = () => ctrl.postMessage({ type: '--' });
}());

最初に`MessagePort`の片側を渡しておくのがキモ。

`Worker#postMessage()`は、第2引数で`Transferable`なものを渡せる。

Worker.postMessage() - Web APIs | MDN

最初は`port1`と`port2`の意味がわからんかったけど、互いに一方交の通信ができるから2つ必要ってこと。

// ServiceWorker
let port;
let count = 0;
self.addEventListener('message', ev => {
  const { type } = ev.data;

  switch (type) {
    case 'init':
      port = ev.ports[0];
      break;

    case '++':
      count++;
      break;

    case '--':
      count--;
      break;

    default:
  }

  port && port.postMessage(count);
});

あとはその`MessagePort`を使って、好きなときに`postMessage()`するだけ。

基本的にBrowser側に主導権があるなら、`Promise`で返すほうがI/F的にはシュッと書けそう。