Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 63 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@

**Socket.io inside a shared WebWorker**
# Socket.io inside a shared WebWorker

Running Socket.io in a shared webworker allows you to share a single Socket.io websocket connection for multiple browser windows and tabs. A drop in replacement for the socket.io client.

https://socket.io/
https://github.com/socketio/socket.io-client

**Quick Install**
## Quick Install

`npm i --save socketio-shared-webworker`
```
npm i --save socketio-shared-webworker
```

**Reason**
## Reason

* It's more efficient to have a single websocket connection
* Page refreshes and new tabs already have a websocket connection, so connection setup time is zero
Expand All @@ -19,7 +21,7 @@ https://github.com/socketio/socket.io-client
* Can be extended as a basis for IPC between your browser windows/tabs
* It's the cool stuff..

**Current Support**
## Current Support

The aim is to support all methods from Socket.io client API.
https://github.com/socketio/socket.io-client/blob/master/docs/API.md
Expand All @@ -34,9 +36,9 @@ Connect and disconnect `io.emit('connect', fn)` is broadcasted to all tabs/windo

Connection Manager `io.Manager` is not yet supported

```
```js
var ws = wio('http://localhost:8000/')
ws.setWorker('shared-worker.js')
ws.useWorker('shared-worker.js')

ws.on('connect', function() {
console.log('connected!')
Expand All @@ -58,53 +60,90 @@ ws.on('error', function (data) {

```

**Install**
## Install

Install locally using npm. (Alternatively clone the repo and look at `index.html` as an example)

`npm install --save socketio-shared-webworker`
```sh
npm install --save socketio-shared-webworker
```

To use in your nodejs based project:
To use in your nodejs project:

```
First make sure `node_modules/socketio-shared-webworker/dist/shared-worker.js` is served by your server.
As an example see `server.js` for an example `express` and `socket.io` server serving `dist/shared-worker.js` as `shared-worker.js` via `express.static`.

You can also copy `dist/shared-worker.js` into your `public/` directory and serve that with `app.use(express.static('./public'))`.

```js
var wio = require('socketio-shared-webworker')
var ws = wio('http://localhost:8000/')
ws.setWorker('node_modules/socketio-shared-webworker/shared-worker.js')

ws.useWorker('node_modules/socketio-shared-webworker/dist/shared-worker.js') // or just shared-worker.js if placed in public/
ws.on('connect', () => {
console.log('connected!')
ws.emit('message', 'Hi There!')
})
ws.on('message', data => console.log('received message', data))
ws.on('disconnect', () => console.log('disconnected!'))
ws.on('error', data => console.log('error', data))
```

Or to use in HTML `wio` is global.

```
<script src="dist/socket.io-worker.bundle.js"></script>
```html
<script src="socket.io-worker.js"></script>
<script>
var ws = wio('http://localhost:8000/')
ws.setWorker('node_modules/socketio-shared-webworker/shared-worker.js')
ws.useWorker('node_modules/socketio-shared-webworker/dist/shared-worker.js')
// use wio like io
</script>

```

Note: `ws.setWorker('node_modules/socketio-shared-webworker/shared-worker.js')` should point to the shared-worker.js url relative to your HTML page base URL. Shared webworkers can only be loaded from the same domain like CORS.
Note: `ws.useWorker('node_modules/socketio-shared-webworker/shared-worker.js')` should point to the shared-worker.js url relative to your HTML page base URL. Shared webworkers can only be loaded from the same domain like CORS.

See `index.html` for an example.

To develop:
### Using a Worker instead of SharedWorker

By default the library will use `Worker` when `SharedWorker` is not available.
If you want to specify using `Worker` specifically then use:

`$ git clone https://github.com/IguMail/socketio-shared-webworker`
```js
var wio = require('socketio-shared-webworker')
var ws = wio('http://localhost:8000/')
ws.setWorkerType(Worker)
ws.useWorker('node_modules/socketio-shared-webworker/dist/shared-worker.js')
```

`$ npm install`
At the moment only `SharedWorker` and `Worker` are supported. `ServiceWorker` is not.

`$ npm start` Starts the socket.io server
### To develop:

`$ npm run dev` Starts development server with HMR
```bash
git clone https://github.com/IguMail/socketio-shared-webworker
cd socketio-shared-webworker
npm install
# Start development server with HMR
npm run dev
```

In chrome visit the URL: chrome://inspect/#workers so see shared webworkers and inspect, debug.
Visit the `index.html` in the browser for the demo.

Production build
### Production build

```bash
npm run build
```

The builds will be placed in `build/` directory. Copy these to your `public/` directory in your server.

To start the http and socket.io server to test the build

`$ npm run build`
```bash
npm start
```


***Based heavily on**
Expand Down
4 changes: 2 additions & 2 deletions dist/shared-worker.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/shared-worker.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/socket.io-worker.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/socket.io-worker.js.map

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions example/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

const wio = require('../src/socket.io-worker')

var ws = wio('http://localhost:3000/')
ws.setWorker('dist/shared-worker.js')
const ws = wio('http://localhost:3000/')
const workerType = confirm('Use SharedWorker? (Will use Worker if no)') ? SharedWorker : Worker

ws.setWorkerType(workerType)
ws.useWorker('dist/shared-worker.js')
console.log('connecting...')

ws.on('connect', function() {
Expand Down
6 changes: 5 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ <h1>Socket.io Share WebWorker client example</h1>

<script>
var ws = wio('http://localhost:8000/')
ws.setWorker('shared-worker.js')
ws.setWorkerType(confirm('Use SharedWorker? (Will use Worker if no)') ? SharedWorker : Worker)
ws.useWorker('shared-worker.js')
console.log('connecting...')

ws.on('connect', function() {
Expand All @@ -25,4 +26,7 @@ <h1>Socket.io Share WebWorker client example</h1>
ws.on('error', function (data) {
console.log('error', data)
})


setTimeout(() => ws.emit('message', 'Hello!'), 1000)
</script>
76 changes: 44 additions & 32 deletions src/shared-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var socket = io(self.name),
ports = [],
socketConnected = false

// handle webworker clients already with ports
// handle shared webworker clients already with ports
socket.on('connect', function(msg) {
socketConnected = true
ports.forEach(function(port) {
Expand All @@ -30,46 +30,58 @@ socket.on('disconnect', function(msg) {
})
})

// handle new clients
// shared worker handle new clients
addEventListener('connect', function(event) {
var port = event.ports[0]
ports.push(port)
port.start()

log('client connected to worker', event)
log('client connected to shared worker', event)

port.addEventListener('message', function(event) {
port.addEventListener('message', event => handleMessage(event, port))
})

// regular worker handle messages
addEventListener('message', event => handleMessage(event, self))
if (typeof Worker !== 'undefined') {
setTimeout(() => postMessage({
type: 'connect',
message: null
}))
}

// handle messages
function handleMessage(event, port) {

var model = event.data
log('port received message', model.eventType, model.event, model.data)
switch(model.eventType) {
case 'on':
const eventName = model.event
if (eventName == 'connect') {
if (socketConnected) {
port.postMessage({
type: eventName
})
}
break;
}
if (eventName == 'disconnect') {
break;
}
socket.on(eventName, function(msg) {
log('socket received message', msg)
var model = event.data
log('port received message', model.eventType, model.event, model.data)
switch(model.eventType) {
case 'on':
const eventName = model.event
if (eventName == 'connect') {
if (socketConnected) {
port.postMessage({
type: eventName,
message: msg
type: eventName
})
}
break;
}
if (eventName == 'disconnect') {
break;
}
socket.on(eventName, function(msg) {
log('socket received message', msg)
port.postMessage({
type: eventName,
message: msg
})
break;
case 'emit':
socket.emit(model.event, model.data) // todo: ack cb
break;
}

})
})
})
break;
case 'emit':
socket.emit(model.event, model.data) // todo: ack cb
break;
}

}

if (typeof module === 'object') module.exports = socket
44 changes: 30 additions & 14 deletions src/socket.io-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ const io = require('socket.io-client')

class SharedWorkerSocketIO {

WorkerType = window.SharedWorker || window.Worker
worker = null
workerUri = null
socketUri = null
events = new EventEmitter()
socket = {}
socket = null

constructor(socketUri) {
this.log('SharedWorkerSocketIO ', socketUri)
Expand All @@ -20,46 +22,49 @@ class SharedWorkerSocketIO {
}

startWorker() {
this.log('Connecting to SharedWorker', this.workerUri)
this.worker = new window.SharedWorker(this.workerUri, this.socketUri)
this.worker.port.addEventListener('message', event => {
this.log('Starting Worker', this.WorkerType, this.workerUri)
this.worker = new this.WorkerType(this.workerUri, {
name: this.socketUri
})
const port = this.worker.port || this.worker
port.onmessage = event => {
this.log('<< worker received message:', event.data.type, event.data.message)
this.events.emit(event.data.type, event.data.message)
}, false)

}
this.worker.onerror = event => {
this.log('worker error', event)
this.events.emit('error', event)
}

this.worker.port.start()
this.log('worker started')
}

emit(event, data, cb) {
this.log('>> emit:', event, data, cb)
if (this.worker) {
// todo: ack cb
this.worker.port.postMessage({
const port = this.worker.port || this.worker
port.postMessage({
eventType: 'emit',
event: event,
data: data
})
} else {
this.socket.emit.apply(this, arguments)
this.socket.emit(...arguments)
}
}

on(event, cb) {
if (this.worker) {
this.log('worker add handler on event:', event)
this.worker.port.postMessage({
const port = this.worker.port || this.worker
port.postMessage({
eventType: 'on',
event: event
})
this.events.on(event, cb)
} else {
this.log('socket add handler on event:', event)
this.socket.on.apply(this, arguments)
this.socket.on(...arguments)
}
}

Expand All @@ -71,18 +76,29 @@ class SharedWorkerSocketIO {
} catch (e) {
this.log('Error starting socket.io shared webwoker', e)
this.log('Starting socket.io instead')
this.worker = null // disable worker
this.startSocketIo()
}
}

setWorker(uri) {
this.log('Setting worker', uri)
setWorkerType(WorkerType) {
this.log('Setting WorkerType', WorkerType)
this.WorkerType = WorkerType
}

useWorker(uri) {
this.log('Starting worker', uri)
this.workerUri = uri
if (!this.started) {
this.start()
}
}

/**
* @deprecated
*/
setWorker = this.useWorker

}

SharedWorkerSocketIO.prototype.log = console.log.bind(console)
Expand Down