https://github.com/kumavis/sw-stream
ServiceWorkerStream and ServiceWorkerGlobalListener
This a utility for creating streams between the page and a servive worker.
在页面也服务器中创建流
usage
in page
pass the registered service worker to create a duplex stream.
传递一个登录的service worker去创建一个双工流
const duplex = SwStream({ serviceWorker: this.serviceWorker.controller })
There is an optional context
property that will be passed along on the initial handshake and retreivable from the messageEvent.data.context
received from the SwGlobalListener.
context参数是可选的,它将会在第一次握手时传递并且会被从SwGlobalListener检索得到的messageEvent.data.context检索到
const duplex = SwStream({ serviceWorker, context })
in ServiceWorker
listen for client connections
const connectionListener = new SwGlobalListener(self) connectionListener.on('remote', (portStream, messageEvent) => { // ... })
看测试了解使用:
sw-stream/test/service-worker.js (即background.js)
const SwGlobalListener = require('../lib/sw-global-listener') const swGlobal = self // setup ServiceWorkerGlobal and capture clients swGlobal.addEventListener('activate', function(event) { event.waitUntil(swGlobal.clients.claim()) }) swGlobal.oninstall = function (event) { event.waitUntil(swGlobal.skipWaiting()) } // listen for clients const connectionListener = new SwGlobalListener(swGlobal) connectionListener.on('remote', (portStream, messageEvent) => { console.log('got a remote connection!') remoteStream.on('data', (message) => { console.log('message:', message) // example: double value and then send back let newValue = message.value * 2 remoteStream.write({ value: newValue }) }) })
sw-stream/lib/sw-global-listener.js
const EventEmitter = require('events') const PortStream = require('./message-channel-port-stream') class SwGlobalListener extends EventEmitter { constructor (swGlobal) { super() swGlobal.addEventListener('message', (messageEvent) => { // validate port if (!messageEvent.data) return if (messageEvent.data.action !== 'handshake') return // process message const port = messageEvent.ports[0] if (!port) throw new Error('Handshake missing port...') // create new portStream const portStream = new PortStream(port) // announce new connection this.emit('remote', portStream, messageEvent) }) } } module.exports = SwGlobalListener
sw-stream/lib/message-channel-port-stream.js
const Duplex = require('readable-stream').Duplex const inherits = require('util').inherits module.exports = MessageChannelPortDuplexStream inherits(MessageChannelPortDuplexStream, Duplex) function MessageChannelPortDuplexStream (port) { Duplex.call(this, { objectMode: true, }) this._port = port port.onmessage = this._onMessage.bind(this) } // private MessageChannelPortDuplexStream.prototype._onMessage = function (event) { const msg = event.data if (Buffer.isBuffer(msg)) { delete msg._isBuffer var data = new Buffer(msg) this.push(data) } else { this.push(msg) } } // stream plumbing MessageChannelPortDuplexStream.prototype._read = noop MessageChannelPortDuplexStream.prototype._write = function (msg, encoding, cb) { try { if (Buffer.isBuffer(msg)) { var data = msg.toJSON() data._isBuffer = true this._port.postMessage(data) } else { this._port.postMessage(msg) } } catch (err) { return cb(new Error('MessageChannelPortDuplexStream - disconnected')) } cb() } // util function noop () {}
const EventEmitter = require('events') const SwStream = require('../lib/sw-stream') module.exports = class ServiceWorkerLauncher extends EventEmitter { constructor (opts) { super() this.serviceWorker = navigator.serviceWorker this.startWorker() .then(registeredWorker => { console.log('setting up stream...') const duplex = SwStream(registeredWorker) duplex.on('data', (chunk) => {console.log('controller saw:', chunk) }) duplex.on('error', (chunk) => {console.log('controller saw:', chunk) }) duplex.write({ value: 42 }) }) .catch(err => { this.handleError(err) }) } startWorker () { // check to see if their is a preregistered service worker if (!this.serviceWorker.controller) { console.log('registering...') return Promise.resolve(this.registerWorker()) } else { console.log('ready') return Promise.resolve(this.serviceWorker.ready) } } registerWorker () { return this.serviceWorker.register('sw-bundle.js') .then(sw => { return sw }) } handleError (err, contextMessage) { if (!err) { console.error(contextMessage) this.emit('error', contextMessage) } else { console.error(err) this.emit('error', err) } } }
const PortStream = require('./message-channel-port-stream') module.exports = SericeWorkerStream function SericeWorkerStream({ serviceWorker, context }) { // create message channel for communication const messageChannel = new MessageChannel() // send handshake including port to respond on serviceWorker.postMessage({ action: 'handshake', context }, [messageChannel.port2]) // construct stream around local message channel port const portStream = new PortStream(messageChannel.port1) return portStream }