ataraxia-services
Advanced tools
Comparing version 0.4.4 to 0.5.0
194
index.js
@@ -19,2 +19,4 @@ 'use strict'; | ||
this.services = new Map(); | ||
this.version = 1; | ||
this.nodes = new Map(); | ||
@@ -31,17 +33,45 @@ network.on('message', this._handleMessage.bind(this)); | ||
_handleNodeAvailable(node) { | ||
/* | ||
* When we connect to a new node, send the node all of the local | ||
* services we provide. | ||
*/ | ||
for(const service of this.services.values()) { | ||
if(service instanceof LocalService) { | ||
node.send('service:available', service.definition); | ||
// Setup the node and initialize the version we have | ||
const nodeData = { | ||
node: node, | ||
version: 0 | ||
}; | ||
this.nodes.set(node.id, nodeData); | ||
// Request a list of services | ||
node.send('service:list', { lastVersion: 0 }); | ||
// Schedule listing around every minute | ||
function scheduleList() { | ||
nodeData.listTimer = setTimeout(() => { | ||
debug('Requesting list of services from', node); | ||
node.send('service:list', { lastVersion: nodeData.version }); | ||
scheduleList(); | ||
}, 60000 + Math.random() * 5000); | ||
} | ||
scheduleList(); | ||
// Set a timer to fallback to broadcasting services | ||
nodeData.broadcastTimer = setTimeout(() => { | ||
nodeData.broadcastTimer = null; | ||
for(const service of this.services.values()) { | ||
if(service instanceof LocalService) { | ||
node.send('service:available', service.definition); | ||
} | ||
} | ||
} | ||
}, 2000); | ||
} | ||
_handleNodeUnavailable(node) { | ||
// Remove the node so we no longer query it | ||
const nodeData = this.nodes.get(node.id); | ||
clearTimeout(nodeData.listTimer); | ||
clearTimeout(nodeData.broadcastTimer); | ||
this.nodes.delete(node.id); | ||
// Remove all the remote services of the node | ||
for(const service of this.services.values()) { | ||
if(service instanceof RemoteService && service.node.id === node.id) { | ||
this._handleServiceUnavailable0(service); | ||
this._handleServiceUnavailable0(node, service); | ||
} | ||
@@ -78,6 +108,12 @@ } | ||
register(id, instance) { | ||
// TODO: How should duplicate services be handled? | ||
const service = new LocalService(this, id, instance); | ||
this.services.set(id, service); | ||
this.network.broadcast('service:available', service.definition); | ||
// Update the version and broadcast this new service | ||
this.version++; | ||
const def = Object.assign({ servicesVersion: this.version }, service.definition); | ||
this.network.broadcast('service:available', def); | ||
this.events.emit('available', service.proxy); | ||
@@ -98,4 +134,8 @@ | ||
this.services.delete(service.id); | ||
this.network.broadcast('service:unavailable', service.definition); | ||
// Update the version and broadcast that this service is no longer available | ||
this.version++; | ||
const def = Object.assign({ servicesVersion: this.version }, service.definition); | ||
this.network.broadcast('service:unavailable', def); | ||
this.events.emit('unavailable', service.proxy); | ||
@@ -114,4 +154,17 @@ } | ||
_serviceUpdated(service) { | ||
// Update the version and broadcast the service | ||
this.version++; | ||
const def = Object.assign({ servicesVersion: this.version }, service.definition); | ||
this.network.broadcast('service:available', def); | ||
} | ||
_handleMessage(msg) { | ||
switch(msg.type) { | ||
case 'service:list': | ||
this._handleServiceList(msg.returnPath, msg.data); | ||
break; | ||
case 'service:list-result': | ||
this._handleServiceListResult(msg.returnPath, msg.data); | ||
break; | ||
case 'service:available': | ||
@@ -141,14 +194,115 @@ this._handleServiceAvailable(msg.returnPath, msg.data); | ||
/** | ||
* Handle an incoming request to list services. | ||
* | ||
* Will check if anything has changed since the requested version and if | ||
* so sends back a complete list of services. | ||
*/ | ||
_handleServiceList(node, data) { | ||
// If the node has seen all of our services, skip reply | ||
if(data.lastVersion === this.version) return; | ||
// Mark that the other other node supports service listing, disable broadcast | ||
const nodeData = this.nodes.get(node.id); | ||
clearTimeout(nodeData.broadcastTimer); | ||
nodeData.broadcastTimer = null; | ||
// Collect the definitions for all of our local services | ||
const services = []; | ||
for(const service of this.services.values()) { | ||
if(service instanceof LocalService) { | ||
services.push(service.definition); | ||
} | ||
} | ||
if(services.length > 0 || data.lastVersion === 0) { | ||
// If we have any services send them back | ||
node.send('service:list-result', { | ||
servicesVersion: this.version, | ||
services: services | ||
}); | ||
} | ||
} | ||
/** | ||
* Handle an incoming result from a `service:list` request. | ||
*/ | ||
_handleServiceListResult(node, data) { | ||
// Figure out which services belong to the node | ||
const servicesNoLongerAvailable = new Set(); | ||
for(const service of this.services.values()) { | ||
if(service instanceof RemoteService && service.node.id === node.id) { | ||
servicesNoLongerAvailable.add(service.id); | ||
} | ||
} | ||
// Handle the incoming list of services | ||
for(const service of data.services) { | ||
// Remove from list of services that are unavailable | ||
servicesNoLongerAvailable.delete(service.id); | ||
// Add or update the service | ||
this._handleServiceAvailable0(node, service); | ||
} | ||
// Remove the remaining services | ||
for(const id of servicesNoLongerAvailable) { | ||
const service = this.services.get(id); | ||
this._handleServiceUnavailable0(service); | ||
} | ||
// Update the version we have seen | ||
const nodeData = this.nodes.get(node.id); | ||
nodeData.version = data.servicesVersion; | ||
} | ||
/** | ||
* Update the last version of a node or request a list of services if | ||
* there is a gap. | ||
*/ | ||
_updateNodeVersion(node, data) { | ||
if(! data.servicesVersion) return; | ||
// Check the version being tracked for the node | ||
const nodeData = this.nodes.get(node.id); | ||
if(nodeData.version === data.servicesVersion - 1) { | ||
// The version we have is the previous one, perform a simple update | ||
nodeData.version = data.servicesVersion; | ||
} else { | ||
// There is a gap in our data, request the list of services | ||
node.send('service:list', { lastVersion: nodeData.version }); | ||
} | ||
} | ||
/** | ||
* Handle that a new service is available or that it has been updated. | ||
*/ | ||
_handleServiceAvailable(node, data) { | ||
debug('Service', data.id, 'available via', node); | ||
// Handle the incoming version information | ||
this._updateNodeVersion(node, data); | ||
// Actually handle the message | ||
this._handleServiceAvailable0(node, data); | ||
} | ||
/** | ||
* Create or update a remote service based on its definition. | ||
*/ | ||
_handleServiceAvailable0(node, data) { | ||
let service = this.services.get(data.id); | ||
if(! service) { | ||
// This is a new service, create it and start tracking it | ||
service = new RemoteService(this, node, data); | ||
this.services.set(data.id, service); | ||
debug('Service', data.id, 'available via', node); | ||
this.events.emit('available', service.proxy); | ||
} else { | ||
service.updateDefinition(data); | ||
this.events.emit('available', service.proxy); | ||
// This is an existing service, update the definition | ||
debug('Service', data.id, 'updated via', node); | ||
if(service.updateDefinition(data)) { | ||
this.events.emit('updated', service.proxy); | ||
} | ||
} | ||
@@ -160,2 +314,5 @@ } | ||
// Handle the incoming version information | ||
this._updateNodeVersion(node, data); | ||
// Get the service and protect against unknown service | ||
@@ -165,9 +322,12 @@ let service = this.services.get(data.id); | ||
this._handleServiceUnavailable0(service); | ||
this._handleServiceUnavailable0(node, service); | ||
} | ||
_handleServiceUnavailable0(service) { | ||
debug(service.id, 'is no longer available'); | ||
_handleServiceUnavailable0(node, service) { | ||
debug('Service', service.id, 'is no longer available via', node); | ||
this.services.delete(service.id); | ||
this.events.emit('unavailable', service.proxy); | ||
// Destroy the service object | ||
service.destroy(); | ||
} | ||
@@ -174,0 +334,0 @@ |
{ | ||
"name": "ataraxia-services", | ||
"version": "0.4.4", | ||
"lockfileVersion": 1, | ||
"requires": true, | ||
"lockfileVersion": 1, | ||
"dependencies": { | ||
"ataraxia": { | ||
"version": "0.4.4", | ||
"resolved": "https://registry.npmjs.org/ataraxia/-/ataraxia-0.4.4.tgz", | ||
"integrity": "sha1-Zrfocc/YElWUxgB/ls0JrOA/W1Y=", | ||
"requires": { | ||
"debug": "3.1.0", | ||
"deep-equal": "1.0.1", | ||
"end-of-stream": "1.4.0", | ||
"msgpack-lite": "0.1.26" | ||
} | ||
}, | ||
"debug": { | ||
"version": "3.1.0", | ||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", | ||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", | ||
"integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", | ||
"requires": { | ||
@@ -13,2 +26,20 @@ "ms": "2.0.0" | ||
}, | ||
"deep-equal": { | ||
"version": "1.0.1", | ||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", | ||
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" | ||
}, | ||
"end-of-stream": { | ||
"version": "1.4.0", | ||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", | ||
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", | ||
"requires": { | ||
"once": "1.4.0" | ||
} | ||
}, | ||
"event-lite": { | ||
"version": "0.1.1", | ||
"resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.1.tgz", | ||
"integrity": "sha1-R88IqNN9C2lM23s7F7UfqsZXYIY=" | ||
}, | ||
"eventemitter2": { | ||
@@ -19,2 +50,17 @@ "version": "4.1.2", | ||
}, | ||
"ieee754": { | ||
"version": "1.1.8", | ||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", | ||
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" | ||
}, | ||
"int64-buffer": { | ||
"version": "0.1.10", | ||
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz", | ||
"integrity": "sha1-J3siiofZWtd30HwTgyAiQGpHNCM=" | ||
}, | ||
"isarray": { | ||
"version": "1.0.0", | ||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", | ||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" | ||
}, | ||
"ms": { | ||
@@ -24,4 +70,28 @@ "version": "2.0.0", | ||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||
}, | ||
"msgpack-lite": { | ||
"version": "0.1.26", | ||
"resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz", | ||
"integrity": "sha1-3TxQsm8FnyXn7e42REGDWOKprYk=", | ||
"requires": { | ||
"event-lite": "0.1.1", | ||
"ieee754": "1.1.8", | ||
"int64-buffer": "0.1.10", | ||
"isarray": "1.0.0" | ||
} | ||
}, | ||
"once": { | ||
"version": "1.4.0", | ||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | ||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | ||
"requires": { | ||
"wrappy": "1.0.2" | ||
} | ||
}, | ||
"wrappy": { | ||
"version": "1.0.2", | ||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" | ||
} | ||
} | ||
} |
{ | ||
"name": "ataraxia-services", | ||
"version": "0.4.4", | ||
"version": "0.5.0", | ||
"description": "Services with RPC and events over Ataraxia mesh network", | ||
@@ -9,6 +9,7 @@ "main": "index.js", | ||
"dependencies": { | ||
"ataraxia": "^0.4.4", | ||
"ataraxia": "^0.5.0", | ||
"debug": "^3.1.0", | ||
"deep-equal": "^1.0.1", | ||
"eventemitter2": "^4.1.2" | ||
} | ||
} |
@@ -45,2 +45,40 @@ # ataraxia-services | ||
## Creat | ||
## Creating services | ||
Services are simple objects: | ||
```javascript | ||
services.register('service-id', { | ||
hello(what='world') { | ||
return 'Hello ' + what; | ||
}, | ||
property: 1234 | ||
}); | ||
``` | ||
The only special part of services is that the `metadata` property is | ||
automatically distributed througout the network. | ||
```javascript | ||
services.register('service-with-metadata', { | ||
metadata: { | ||
type: 'cookie-factory' | ||
}, | ||
cookiesMade: 0, | ||
makeCookie() { | ||
return ++this.cookiesMade; | ||
} | ||
}); | ||
``` | ||
The metadata can be access on any node: | ||
```javascript | ||
const service = services.get('service-with-metadata'); | ||
if(service) { | ||
console.log(service.metadata.type); | ||
} | ||
``` |
@@ -73,3 +73,3 @@ 'use strict'; | ||
[metadataChanged]() { | ||
this.parent.network.broadcast('service:available', this.definition); | ||
this.parent._serviceUpdated(this); | ||
} | ||
@@ -76,0 +76,0 @@ |
'use strict'; | ||
const Service = require('./service'); | ||
const deepEqual = require('deep-equal'); | ||
const customInspect = require('util').inspect.custom; | ||
@@ -27,3 +28,8 @@ | ||
updateDefinition(def) { | ||
this.metadata = def.metadata; | ||
if(! deepEqual(this.metadata, def.metadata)) { | ||
this.metadata = def.metadata; | ||
return true; | ||
} | ||
return false; | ||
} | ||
@@ -126,3 +132,3 @@ | ||
remove() { | ||
destroy() { | ||
for(const promise of this.promises.values()) { | ||
@@ -129,0 +135,0 @@ promise.reject(new Error('Service is no longer available')); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23009
713
84
4
+ Addeddeep-equal@^1.0.1
+ Addedataraxia@0.5.2(transitive)
+ Addedhas-proto@1.1.0(transitive)
+ Addedhas-symbols@1.1.0(transitive)
- Removedataraxia@0.4.4(transitive)
- Removedhas-proto@1.0.3(transitive)
- Removedhas-symbols@1.0.3(transitive)
Updatedataraxia@^0.5.0