[譯]如何在Service Worker和網頁客戶端之間傳送訊息

violinux666發表於2018-12-29

原文

Service Workers是一個為頁面工作的後臺處理器。提供離線web apps是Service Workers目前最讓人感興趣的功能,同時Service Workers能夠管理一個本地的資源快取,當網路連線狀態是正常的時候,這個本地資源快取能夠自動跟伺服器進行同步。這是十分酷的,但我想談一下Service Workers的另一個用途,使用它來管理多個web頁面之間的通訊。

例如,你可能有一個應用開啟在多個瀏覽器頁籤中。Service Workers能夠更新一個頁籤當其他頁簽有一個事件觸發,也可以做到當伺服器發出一個訊息後,所有頁籤的內容將被更新。

一個Service Workers可以控制多個客戶端頁面,例如ServiceWorker會自動的控制它範圍內的所有客戶端頁面,它的範圍是指你站點下的url,通常來講是Service Worker script檔案的路徑。

在這個demo裡,我們將使用三個檔案 client1.html client2.html service-worker.js

首先我們註冊service worker 在 client1.html

<!doctype html>
<html>
<head>
    <title>Service Worker - Client 1</title>
</head>
<body>
    <script>
        if('serviceWorker' in navigator){
            // Register service worker
            navigator.serviceWorker.register('/service-worker.js').then(function(reg){
                console.log("SW registration succeeded. Scope is "+reg.scope);
            }).catch(function(err){
                console.error("SW registration failed with error "+err);
            });
        }
    </script>
</body>
</html>
複製程式碼

接著我們建立一個基本的 Service worker 在service-worker.js

console.log("SW Startup!");

// Install Service Worker
self.addEventListener('install', function(event){
    console.log('installed!');
});

// Service Worker Active
self.addEventListener('activate', function(event){
    console.log('activated!');
});
複製程式碼

我不會去解釋他是怎麼工作的,因為在很多地方都有記錄(譯者注:作者的意思是很多地方都有console.log)

我們同時建立了client2.html 我們只會在client1註冊serviceworker,所以不需要有重複的程式碼在這裡。serviceworker執行的時候將會自動的控制它的作用域下的頁面。

<!doctype html>
<html>
<head>
    <title>Service Worker - Client 2</title>
</head>
<body>
    <script>
    </script>
</body>
</html>
複製程式碼

如果你在瀏覽器上訪問client1.html你應該會看到由console.log輸出的註冊資訊。在Chrome(48+)中,你可以在開發工具的“Resouces”選項卡下點選“inspect”,為服務工作者開啟一個檢查器。(譯者注:這個我沒有找到)。當你開啟client2.html在新的瀏覽器頁籤,你可以在開發工具內的“Controlled Clients”找到它

現在我們可以繼續講有趣的東西了。


首先我們讓客戶端發訊息給serviceworker。所有我們需要去加一個訊息的處理在service-worker.js

self.addEventListener('message', function(event){
    console.log("SW Received Message: " + event.data);
});
複製程式碼

現在我們增加一個發訊息的函式在兩個客戶端裡

function send_message_to_sw(msg){
    navigator.serviceWorker.controller.postMessage("Client 1 says '"+msg+"'");
}
複製程式碼

如果你在客戶端頁面的控制檯內呼叫send_message_to_sw("Hello"),你因該可以在serviceworker的控制檯內看到有訊息顯示

我們可以進一步的讓serviceworker去響應客戶端發來的訊息。實現它我們需要去改良我們的send_message_to_sw函式。我們使用‘Message Channel’,Message Channel能夠提供了一對埠(port)來進行通訊。我們將一個引用連同訊息一起傳送到埠的另一端,所以Service Worker能夠使用它去進行答覆。我們也可以對這些響應訊息做一些處理。為了方便起見,我們還使用Promise來處理等待響應。

譯者注:這裡說的埠(port)用於頁面與serviceworker之間的通訊

function send_message_to_sw(msg){
    return new Promise(function(resolve, reject){
        // Create a Message Channel
        var msg_chan = new MessageChannel();

        // Handler for recieving message reply from service worker
        msg_chan.port1.onmessage = function(event){
            if(event.data.error){
                reject(event.data.error);
            }else{
                resolve(event.data);
            }
        };

        // Send message to service worker along with port for reply
        navigator.serviceWorker.controller.postMessage("Client 1 says '"+msg+"'", [msg_chan.port2]);
    });
}
複製程式碼

在*service-worker.js *我們修改了監聽器,與訊息一起傳送響應在埠

self.addEventListener('message', function(event){
    console.log("SW Received Message: " + event.data);
    event.ports[0].postMessage("SW Says 'Hello back!'");
});
複製程式碼

現在如果在你的客戶端控制檯執行send_message_to_sw("Hello").then(m => console.log(m)),你將看到資訊顯示在serviceworker的控制檯裡,在客戶端的控制檯將會有答覆。請注意,我們使用Promise then函式來等待響應和箭頭函式,因為這樣更容易去測定(譯者注:這裡type我翻譯成控制)。

現在我們有了一個讓客戶端發訊息給serviceworker同時serviceworker能夠答覆的機制。您可以使用它讓客戶機檢查長時間執行的流程的狀態,讓serviceworker將訊息轉發給所有客戶端或其他一些很酷的東西。


現在我們允許serviceworker廣播一個事件到所有的客戶端讓所有客戶響應。這與以前使用的機制類似,只是角色顛倒了。

首先我們在客戶端增加一個訊息監聽器,我們增加了測試serviceworker相容性的程式碼,其他的地方几乎相同。

if('serviceWorker' in navigator){
    // Handler for messages coming from the service worker
    navigator.serviceWorker.addEventListener('message', function(event){
        console.log("Client 1 Received Message: " + event.data);
        event.ports[0].postMessage("Client 1 Says 'Hello back!'");
    });
}
複製程式碼

接著我們給serviceworker增加一個傳送訊息給客戶端的函式。這也跟之前很類似,只是我們需要提供給一個客戶端物件(一個頁面的應用),這個物件能告訴我們要往哪裡發訊息。

function send_message_to_client(client, msg){
    return new Promise(function(resolve, reject){
        var msg_chan = new MessageChannel();

        msg_chan.port1.onmessage = function(event){
            if(event.data.error){
                reject(event.data.error);
            }else{
                resolve(event.data);
            }
        };

        client.postMessage("SW Says: '"+msg+"'", [msg_chan.port2]);
    });
}
複製程式碼

serviceworker API提供了獲取所有已連線客戶端引用的介面。我們可以將其封裝在一個方便的函式中,以便向所有客戶機廣播訊息(注意,我們再次使用箭頭函式)。

function send_message_to_all_clients(msg){
    clients.matchAll().then(clients => {
        clients.forEach(client => {
            send_message_to_client(client, msg).then(m => console.log("SW Received Message: "+m));
        })
    })
}
複製程式碼

現在如果我們在serviceworker的控制檯內執行send_message_to_all_clients('Hello'),您將看到在所有客戶端控制檯中接收到的訊息,以及在serviceworker控制檯中客戶機的響應。

相關文章