One of the cool features of Couchbase Lite that hasn’t been featured much is an ability to do P2P replication between two devices. Couchbase Lite is packaged with an extra component called Couchbase Lite Listener that allows your application to accept HTTP connections from other devices running Couchbase Lite and sync data with them.
A couple months ago, Wayne Carter (Chief Architect of Mobile) and Traun Leyden (Android engineer) skyped me in and sold me an idea about creating a simple P2P photo sharing app that can demonstrate how easily you can develop a P2P application using Couchbase Lite. I was engaged since then and spent a couple days working on this little P2P iOS photo sharing app called PhotoDrop. In this blog post, I will show you how I used Couchbase Lite to develop the application. This is what you'll end up with at the end:
PhotoDrop is a P2P photo sharing app similar to the iOS AirDrop feature that you can use to send photos across devices. Before jumping into the code, I would like to briefly discuss P2P concerns and design choices that I made when developing the application.
Peer Discovery can be done in several ways. In iOS, you can use the Bonjour Service for discovering peers but this could be an issue if you later want to develop the application in other platforms. In PhotoDrop, I am using a simpler and more direct way using a QRCode. I use the QRCode to advertise an adhoc endpoint URL that a sender can scan and send photos to.
Peer Identity is a related subject to the Peer Discovery. Peer Identiy is normally difficult to solve without introducing some undesired steps into the application such as user registration, user login, and peer approval. By using a QRCode, and requiring the two people to have direct explicit interaction to send and receive photos, the issue is mitigated.
Authentication is needed to ensure that the access control, for this case the write access to push photos into another peer's database, is granted to the right person. In PhotoDrop, I am using the Basic Auth that Couchbase Lite has already supported. I securely generate a one-time username and password, bundle them with the URL endpoint and encode them all in the QRCode presented by the receiver to the sender. Once the sender scans the QRCode, the sender will have the username and password for basic authentication.
Secure Communication Channels are requried especially for sending sensitive information. I did not implement secure communication in this app. However, recently Jen Alfke added TLS support including an API to generate a self-signed certificate on the fly to the iOS Couchbase Lite Listener. As all the hard work has been done, You can add support for this pretty easily.
After all, the current flow of the PhotoDrop is fairly simple. You select photos you want to share to your friend and open the QRCode scanner to scan the target endpoint that the selected photos will be sent to. On the other side, your friend opens the appliation, shows the QRCode and waits for you to scan and send the photos. The next section will provide all of the implementation details for the application.
PhotoDrop uses ALAssetsLibrary to access to the photos in the Camera Roll album on an iOS device because the UIImagePickerViewController that doesn't support multiple photo selection.
The photos are displayed in a UICollectionViewController. A simple custom UICollectionViewCell named PhotoViewCell is created to display a photo thumbnail and a checkbox indicating the selected status of the photo.
When the selected photos are ready to send and the send button is touched, the SendViewController will be presented with the selected photos.
Once the SendViewController is presented, we find ourselves in the viewDidLoad() function in which we get an empty database object named “db” from the DatabaseUtil class. The reason that we get a fresh database is to ensure that there are no pending documents from a previous sharing session.
The getEmptyDatabase() function of the DatabaseUtil class returns an empty database with the given name by deleting the database if it does exist and recreating a new one.
Once the SendViewController is presented, we will be in the viewDidAppear(animated:Bool) function. In the viewDidAppear function, we start the AVFoundation's QRCode capturing session to present the QRCode scanner.
When a QRCode is captured, we extract the endpoint URL which is the remote URL that we will send photos to. The core code for creating and sending photo documents begins and ends in the replicate(url: NSURL) function which has about 50 lines of code.
The code starts with looping thru each photo in the sharedAssets array. For each photo, we get a binary representation and attach to an empty Couchbase Lite document.
After we finish creating the photo documents, we send those documents to the other device. To do this, We create a push replicator and set the ids of the documents that we would like to replicate. In general, setting the document ids for the replicator is optional. If ids is not supplied, the the replicator would just replicate all of the documents in the database.
Before we start the replicator, we setup a notification observer to observe the replicating status so that we can display replication status appropriately when the replication in in progress.
Receiving photos is done in ReceiveViewController which is presented from the ViewController screen when a user touches the Receive button. When the ReceiveViewController is presented, we get a fresh database named “db” in the viewDidLoad() method. In the viewDidAppear(animated: Bool) function, we call startListener() to create and start a CBLListener object. The CBLListener is an embedded lightweight HTTP server which listens to HTTP requests and routes those requests to appropriate handlers to perform replication operations. Once the listener is started, we can obtain the listener's URL and present a QRCode encoding the URL. As a bonus, iOS CoreImage supports a QRCode filter so generating a QRCode image is really easy :)
When we setup the listener in startListener(), we enable Basic Authentication with the generated username/password pair. The generated username/password pair serves as a one-time pair that provides a secure (enough) solution to prevent unauthorized users from pushing images to the receiver. To generate the username and password, we use the iOS Randomization Services API (SecRandomCopyBytes) that generates cryptographically secure random values.
At the end of the startListener() function, the startObserveDatabaseChange() function is called to observe any changes happening to the database via notifications named kCBLDatabaseChangeNotification. We are now able to observe when the photo documents from the other device are replicated and save the photos into the device's camera roll.
The function to save a photo into to the device's camera roll is below. The function simply gets the image from the document attachment, creates a CGImage and saves it to the camera roll via the ALAssetsLibrary's writeImageDataToSavedPhotosAlbum() function. After saving a photo, we display a thumbnail on the screen.
There are many ways to develop the PhotoDrop app, and using Couchbase Lite is perhaps one of the easiest. The core code for sending and receiving photos is barely 100 lines of code and contains zero lines of code directly involved in network communication. I hope this blog post and the PhotoDrop application itself gives you some inspiration and ideas for using Couchbase Lite for P2P applications. Clone the PhotoDrop GitHub repo, play with it, and let me know what you think!