SmarterSockets
SmarterSockets is a websocket implemention backed by AWS IoT & DynamoDB that provides the following functionallity:
- Provides an interface for request => response logic
- Allows for messages to be persisted if client does not disconnect gracefully. Upon reconnect the messages will be sent down to the client.
##Getting Started
Installation
npm install @smarterservices/smartersockets --save
Configuration Options
device
:
host
: The full host for the IoT endpoint. e.g. randomidhere.iot.us-east-1.amazonaws.com requiredclient_id
: A unique client id that will be used to connect to the socket. Value much be unique requiredaccess_key_id
: The AWS temp access key for the connection. requiredsecret_access_key
: The AWS temp secret key for the connection. requiredsession_token
: The AWS security token that is to be used for the temp credentials above. Only passed if your using temp credentials
state_management
: An object containing the preferences for the state management. Only required if you plan to have state managed
ping_interval
: The number of seconds the heartbeat message should be sent from the client. Default: 2 seconds.sync_interval
: The interval (in seconds) that should be used to check the status of all other clients.disconnect_threshold
: The number of seconds that should be used as the threshold for when a client is assumed to be disconnected and can the client.disconnect
event can be fired.removal_threshold
: The number of seconds that should be used as the threshold for when a client should actually be removed from the internal data storage and the client.removed
event emitted.
Example
var config = {
"device": {
"host":"randomidhere.iot.us-east-1.amazonaws.com"
"client_id":"some-client-id"
"access_key_id": "mykey",
"secret_access_id": "mysecret",
"session_token": "a023unadnasona..."
},
"state_management" : {
"ping_interval" : 2,
"sync_interval" : 5,
"disconnect_threshold" : 10,
"removal_threshold" : 60
}
};
state_management only required if using state manage from below
Sample Usage(no state management)
var smartersocket = require('@smarterservices/smartersockets')
var socket = new smartersocket(config)
socket.register(DATA_OBJ).res(CALLBACK_FUNCTION)
##Methods provided
register()
Will publish to session_id+'_input session id must be inside data_object
description: Will both publish to your topic the data provided and register that publish with and id and callback for when the response is recieved.
Required params:
data_object: the object sent to iot. Look below
res: the callback function tagged for when a response is received
Uses promise style for callback registration so the proper format for params is:
socket.register(data_object).res(callback_function)
publish()
description: Same as socket.register but you do not register a callback and should not expect a res from what your publishing.
Required params:
data_object: the object sent to iot. Look below(Publish payload)
socket.publish(data_object)
addListener()
description: Assignes a new listener to a new topic and if using state manager will start sending heartbeats and the inital knock.
Required params:
topic_id: the topic you will start listening on
socket.addListener('topicname');
You will now start recieveing emits to any message sent to topicname
setInterval()
description: This function is used to assign a function that will be set to resolve in an interval and publish to the topic with a callback.
Required params:
resolve_function: Function that needs to resolve and return a data_object in the standard format(Publish payload below).
timer: time in milliseconds to reresolve and publish.
callback_function: function that will be registered and called when the published message gets a response.
EX:
socket.setInterval(function () {
return {
type: 'TYPE_HERE',
topic: 'TOPIC',
data: {
test_data: 1
}
}
}, 5000, function (res) {
console.log(res);
})
close()
Call close to pulish a message to everyone else listening letting them know they can remove you from state.
Required params:
topic_id: 'topic to remove yourself from'
socket.close('TOPIC')
registerTypeCallback()
Call used to register a callback function based on type if one is not registered via message_id. In globa name space so avoid the following names as first params
addListener
close
registerTypeCallback
listen
register
publish
setInterval
attachManager
fire
knock
heartbeat
client_removal
Required params:
type: 'they type your registering a callback for'
callback: 'function that is ran when message with that type is recieved
socket.registerTypeCallback('type_name',CALLBACK FUNCT)
fallback function arguments are (topic_id,obj)
##Publish payload
Any message sent via .register() should follow this standard for payload.
If you pass client_id, timestamp or message_id in it will overwrite what would normally be handled in the module
payload: {
type: The type of message. This is used to start a lambda based on it. IE 'speed_check',
topic_id:Required as its used as the topic to send to.
data: {OBJECT containing any of the data needed for the worker}
}
socket.register(data_object).res(callback)
##Returned payload
Any message sent via .register() that is published back from a lambda
worker will follow this standard:
payload: {
type: 'reply', //will always be reply
topic_id:The topic sent to,
client_id: id of the client the payload is comming from
data: {OBJECT containing any of the data needed for the worker},
message_id: 'Id that was assigned to message on send',
timestamp: 'Timestamp of return'
}
Events Emitted
socket.on('message',function(topic,res){})
description: generic listener for anything not expecting a res from .register method.
Example:
socket.on('message',function(topic,res) {
console.log(topic,res
}
socket.on('connect',function(){})
description: called when connect. Should always nest all other listeners inside this.
Example:
socket.on('connect',function(){
console.log('now connected');
};
**socket.on('reconnect',function(){})**
description:If reconnect happens.
Example:
socket.on('reconnect',function(){
console.log('now reconnected');
};
**socket.on('error',function(error){})**
description: happens if error happens during connect or publish.
Example:
socket.on('error',function(error){
console.log(error);
};
**socket.on('offline',function(){})**
description: happens if state is switched to offline.
Example:
socket.on('offline',function(){
console.log('now offline');
};
**socket.on('close',function(){})**
description: happens if socket connection is closed.
Example:
socket.on('close',function(){
console.log('now closed socket');
};
###All events after this only avaliable with state management class below:
socket.on('client.connected',function(obj){})
description: Happens if a new client connects to your topic
return: OBJECT {topic:topic,client:client_id}
Example:
socket.on('client.connected',function(res){
console.log(res.topic,res.client);
};
**socket.on('client.disconnected',function(obj){})**
description: Happens if an already connected client disconnects
return: OBJECT {topic:topic,client:client_id}
Example:
socket.on('client.disconnected',function(res){
console.log(res.topic,res.client);
};
**socket.on('client.reconnected',function(obj){})**
description: Happens if a disconnected client reconnects
return: OBJECT {topic:topic,client:client_id}
Example:
socket.on('client.reconnected',function(res){
console.log(res.topic,res.client);
};
**socket.on('client.removed',function(obj){})**
description: Happens when a client ends their session and will not be reconnecting
return: OBJECT {topic:topic,client:client_id}
Example:
socket.on('client.removed',function(res){
console.log(res.topic,res.client);
};
Nest all listeners inside of socket.on(connect)
Must be called after connect has been emitted
Client State & Restoring Data
The system allows for persisting data if/when a client does not reconnect gracefully. This is enabled largely by the ability of AWS IoT to stream data directly into a DynamoDB table. For our purposes, all messages that are sent for topics will be inserted into a DynamoDB table via explicit rules that are setup in the AWS IoT configuration.
Getting started:
Socket can work alone without stateManager but in order to track state you need to pass socket into state manager and then register the manager with socket like follows:
var socket = new socket(configs)
var manager = new stateManager(socket)
socket.attachManager(manager)
Note this needs to happen BEFORE you call socket.addListener()
=======
Handling Client State
Client state is handled by the internal library in a simple json object. When a client initially listens to a topic the sync
function will also be put into a repeat loop behind the scenes. This function will be called repeatedly based on the interval provided when the socket was opened via the configuration variable state_management.sync_interval
.
State management class
State management is all handled in the stateManagement.js
class, a high level overview of the functions the class provides is below.
constructor(state_management_config, socket)
Creates an instance of the state management class. When this method is called the following should happen:
- Call the
sendHeartbeat
function. To let everyone know that they are now online. - Call the
knock
function to get everyone that is listening.
sendHeartbeat(topic_id, client_id, data)
This function is called with the a message with message_type
heartbeat comes in. This will make sure the internal data storage is updated with the most current information for the client that emitted the message.
clientUpdate(topic_id, client_id, data)
This function is called with the a message with message_type
heartbeat comes in. This will make sure the internal data storage is updated with the most current information for the client that emitted the message.
If the client is newly added to the data store then the client.connected
event should be emitted.
clientDisconnected(topic_id, client_id)
Called when a client is to be marked as disconnected. This function should remove the client and emit the client.disconnected
event with the proper data.
Removed(topic_id, client_id)
Called when a client is to be removed. This function should remove the client and emit the client.removed
event with the proper data.
sync()
Reviews all clients and makes some decisions based on the thresholds provided if clients are still connected, disconnected, or need to be removed.
This function should be called repeatedly based on the value in state_management.sync_interval
.
knock(topic_id)
Sends a message out on the topic with the message_type = knock
. This will trigger all listening clients to reply with an immediate heartbeat message. When each message comes back in this will naturally call the clientUpdate()
function to update their state/data within the local datastore.
Internal state management data model
Client state is mananged in memory in a simple object in the form:
topic_id
: The topic_if for which the state is being managed for.
client_id
: The id of the client.
state
: The current state of the client. Valid options are connected
or disconnected
.first_seen
: The unix timestamp the client was first seen as added to the topic.last_seen
: The unix timestamp the client was last seen on the topic - IE the last time a heartbeat message was received from the client.label
: The label for the client.meta
: An object of key/value paris containing any other meta data for the client.
{
"topic1" : {
"client1" : {
"first_seen" : 1459436043,
"last_seen" : 1459436043,
"label" : "Sample User",
"meta" : {
"role" : "Proctor",
"email" : "sample@proctor.com"
}
},
"client2" : {... },
"client3" : {... }
},
"topic2" : {...},
"topic3" : {...}
}
Events Emitted
client.connected
Emitted when a client has connected
client.disconnected
Emitted when a client has disconnected
client.reconnected
Emitted when a client has reconnected
client.removed
Emitted when a client has been removed.
Reserved Message Types
heartbeat
- Message indicates the client is still on the topic and contains details about the client.
knock
- Triggers all clients to immedtately send their heartbeat message.
client_disconnect
- Lets all clients know they have disconnected.
client_removal
- Lets all clients know they should be removed from the topic.