Tutorial: Chat Service

This page walks you through the full process of building the backend for a simple P2P Chat application.

In this example we’ll build a class called Chat which can be used by a front-end GUI. We’ll start with the basics of initializing the API and connecting to a Network; see the QuickStart for more detail on this process.

import json
import time
from pipes import PlatformAPI

class Chat(object):

    def __init__(self, apiPath):
        self.api = PlatformAPI(apiPath)
        self.node = None
        self._listener = None

    def isUserConfigured(self):
        return self.api.hasLocalIdentity()

    def setupUser(self, userName):
        if self.isUserConfigured():
            raise Exception("User is already configured: {}".format(self.api.localIdentity().name))
        self.api.addIdentity(userName)

    def connectToNetwork(self, networkId):
        if self.node:
            raise Exception("Already connected!")
        network = self.api.networkWithId(networkId)
        self.identity = self.api.localIdentity()
        self.node = network.nodeWithIdentity(self.identity)
        self._setupPipeline()
        self.node.connect()

This provides an API for configuring the user name and the basics of connecting to a network. This assumes that the indicated networkId is already configured in the API; see the QuickStart for more detail on this process.

We need to define the _setupPipeline() method, which configures a Pipeline on the network for use with our chat application:

def _setupPipeline(self):
    self.pipeline = self.node.pipelineNamed('CHAT')
    if not self.pipeline:
        self.pipeline = self.node.createPipeline(name = 'CHAT')
    self.pipeline.addPacketListener(self._packetListener)

And finally, we need to manage the sending and receiving of messages. For simplicity we use JSON payloads for the messages:

def sendMessage(self, text):
    assert(self.node)
    message = { "text" : text, "sender" : self.identity.name, "timestamp" : time.time() }
    self.pipeline.putPacket(json.dumps(message).encode())

def _packetListener(self, packet, pipeline):
    message = json.loads(packet.data.decode())
    if self._listener:
        self._listener(message)

def setListener(self, listener):
    self._listener = listener

The sendMessage method creates a JSON dictionary using the indicated text, the local username, and a timestamp to indicate the time the message was entered. This is then placed on the pipeline using the putPacket method.

The _packetListener method is attached to the Pipeline, and is called anytime a Packet is put on the pipeline, from any Node in the Network. This decodes the JSON dictionary from the packet and passes it on to the UI listener, which is configured using setListener().

So, for a UI to use this backend, it simply needs to:

  • Create an instance of the Chat service

  • Initialize it with the proper path, and do some one-time setup including querying the user for a username

  • Use setListener() to receive callbacks whenever messages are available

  • Use sendMessage() to send a chat from the local user upon request

The Pipes API takes care of connecting the Nodes and routing Packets between them. This Chat application will already support an arbitrary number of Nodes. As it is now, messages are only received if you are on the Network when they are sent; the next step is to use persistent Pipelines to ensure messages are always delivered.

Persistent Chat

For persistent chat, we only need to change the one line in the _setupPipeline method where we configure the pipeline:

self.pipeline = self.node.createPipeline(name = 'CHAT', persistent = True)

That’s it! This backend now guarantees that chats are published to the Network and delivered to all Nodes as soon as possible. This includes offline work: if you send a chat when your Node is not connected, it will be cached locally and delivered when you next connect.

Multiple Channels

TODO: brief overview

Private Chat

TODO: using a point-to-point pipeline

Encrypted Chat

TODO: using encrypted Pipelines and sharing secrets