Powered by uWebSockets.js
The intention of our messaging protocol and chat server, which are different from others, is to provide an alternative solution to the wide spread XMPP messaging protocol (and to be honest — the only wide spread ‘standard’ these days).
Read complete medium posts Introducing SAMA and What is SAMA about what is SAMA and what problems we are trying to solve with it:
- https://medium.com/sama-communications/introducing-sama-simple-but-advanced-messaging-alternative-chat-server-524a532e2040
- https://medium.com/sama-communications/what-is-sama-a6d9045fd69a
Clients:
- Frontend Web app https://github.com/SAMA-Communications/sama-client
- Flutter app https://github.com/SAMA-Communications/sama-client-flutter
Local Development / Compete Deployment & E2E Tests
The whole SAMA stack can be tested using https://app.samacloud.io public cloud.
API reference: https://docs.samacloud.io
Also, there is a set of detailed articles for each API on Medium:
Join our community for support and discussions:
- GitHub Issues - SAMA server, GitHub Issues - SAMA client, GitHub Issues - SAMA Flutter client
- SAMA on Medium
- Get help - Discord 💬
Any thoughts, feedback is welcome! Please create a GitHub issue for any feedback you have.
Want to support us?
Record key in redis like sama-node-data:{node-endpoint} where {node-endpoint} ws url like ws://192.168.1.13:55495/, so redis record will be sama-node-data:ws://192.168.1.13:55495/. The record is HSET with ttl that equal config value ws.cluster.nodeExpiresIn in seconds + 5 seconds. The record has key/values like ip/port/host etc.
Record key in redis like sama-node-users:{node-endpoint}. The record is SET that contains list user users connected to this node, user item is string like {organizationId}:{userId}:{deviceId}. Item example: 683db8ecdb9dee54f53304c0:683db9ed29aecb9feb4afb73:38400000-8cf0-11bd-b23e-10b96e40000d
Record key in redis like sama-user-devices:{organizationId}:{userId}. The record is SET that contains list user devices ids connected to all nodes. Item example: 38400000-8cf0-11bd-b23e-10b96e40000d
Record key in redis like sama-user-data:{userId}:{deviceId}. The record in HSET that contains extra user session data e.g. active/inactive status
Each node write record key in Redis like sama-node-data:{node-endpoint} where {node-endpoint} it is own ws url. Node create this record on start and update on sync cluster (check method syncCluster). In sync method node retrieve all records from Redis with prefix sama-node-data:, the parse record ws url part ({node-endpoint}) and try establish ws connection if it doesn't already exist.
If node-A does not have establish connection with node-B, node-A will create ws connection (check method createConnectionWithNode). When ws with node-B endpoint (node-endpoint) successfully opened - node-A send like 'ping' message with own network data (ip/port/host etc.) to node-B (check method shareCurrentNodeInfo), node-B send to node-A like 'pong' message with own network data, node-A after receiving 'pong' message - connection successfully established and handshake finished, app resolve promise and save ws connection in local object (check prop clusterNodesConnections). Node-B after send 'pong' message start connecting with node-A by same flow
If connecting breaks between 2 nodes, node detect it by ws event close, and start reconnecting (check method startNodeReconnecting and promiseQueueWithJittering, prop closeNodesConnections), this method try 3 time create connection until the first successful with delay '1/4/9 seconds', flow like 1 second delay - try connect (if success return) - 4 seconds delay - try connect (if success return) - 9 seconds delay - try connect (if success return) - failed
2 Ways (node-A,node-B - running, node-C - new node in cluster)
-
Flow
How nodes discover each otherand thenHow nodes connect to each other. Node-C retrieve redis records (sama-node-data) created by node-A,node-B and create ws connections with each node (open ws and then connect flow with 'ping'/'pong'). -
Node-A,node-B in method
syncClusterwith call with interval that equal config valuews.cluster.nodeExpiresInin ms, retrievesama-node-datarecords and see new node record created by node-C and then create connection with node-C
Example with node-A and node-B
When node-B disconnect from cluster, ws connection breaks and node-A start reconnecting with node-B but it will not be success.
Then on next syncCluster iteration (or next + 1 iteration) node-A can't find node-B record in sama-node-data records, but has active reconnecting state with node-B, then mean that node-B destroyed because no-one update node-B sama-node-data record ttl, then node-A cancel reconnecting and clean redis data creates by node-B (sama-node-users/sama-user-devices/sama-user-data), sama-node-data already deleted by redis ttl.
It can be detected no syncCluster iteration
- If have active node record
sama-node-usersw/o ttl, but do not have recordsama-node-data- no-one update record so node is destroyed - If do not have node record
sama-node-databut has reconnecting state - If have connection (check prop
clusterNodesConnections) but do not have recordsama-node-data(case when some how ws close was not called)
Check method cleanDestroyedNodeData that clean destroyed node data by node-endpoint.
Flow:
- retrieve
sama-node-users- list of user who was connected to destroyed node - delete 'smembers' from
sama-user-devices - delete
sama-user-datarecord - delete
sama-node-users

