Messaging

The term "messaging" has different meanings for different applications. In a traditional messaging app, a message is simply something the user types in. But in another app, a "message" may be a JSON structure used to signal information, such as a collaboration request.

In ZeroDark.cloud, a "message" can be whatever you want it to be. You simply supply raw bytes, and the framework handles encrypting and sending those bytes. So the content & format of a "message" is completely up to you.

Further, ZeroDark.cloud supports 3 different styles of messaging, all built atop the same primitives. This allows you to pick the style(s) which work best for your application.

 


Styles of Messaging

There are 3 different styles of messaging. A brief overview of each style:

Email Style

If Alice wants to send a message to Bob, then she places the message in her outbox container. The message is then copied into Bob's inbox container. This way, all of Alice's devices see the outgoing message (via her outbox). And all of Bob's devices see the incoming message (via his inbox).

Collaboration Style

Every user has their own treesystem in the cloud. And it's easy to share a branch of the tree with other users simply by giving them the proper permissions to read and/or write. For example, if Alice & Bob (and maybe Carol, Dan, Emily, …) are all collaborating on something, then if Alice puts a file/record into the shared branch, all other users with access to the branch will be notified of the change.

For historical reasons, people tend to think of messaging & sync as different things. But as multi-device support has become standard, they've become nearly identical in practice. In either case, a file/record is uploaded to the cloud. And the intent is that all the sender's devices can see the change. And all the receiver's devices can see the change. As you just read, this can be accomplished either with a separate inbox/outbox concept, or with a shared space. You can decide which approach is preferred for your application.

Signalling Style (lightweight)

One of Alice's devices simply places a message into Bob's inbox. All of Bob's devices see the message. And that's it. Alice's other devices aren't notified of the activity, meaning there's no corresponding overhead/chatter for Alice's devices. This is a lightweight approach, and is generally used for lightweight signalling.

 


Messaging in Practice

We all have an anchor in our mind of how "messaging should work". But it's more pragmatic to consider all the possibilities, and choose based on merits.

Email-style messaing is usually more appropriate when all parties need their own separate copy of the data. As with email, the receiver can delete his/her copy of the message, without affecting the sender's copy.

Collaboration-style messaging is usually more appropriate when parties can share a single copy of the data. This is often the case when there is a clear "owner" of a shared resource. For example, a shared Todo list, as in the ZeroDarkTodo sample application.

 


Email-style messaging

Every user has their own treesystem. And within the treesystem are top-level containers. There are 2 special containers for messaging: outbox & inbox. As you would expect, the outbox is for outgoing messages, and the inbox is for incoming messages. As such, only the owner has permission to write into the outbox. And the inbox is permissive, allowing other users write-only access.

Sending a message is easy:

zdc.databaseManager?.rwDatabaseConnection.asyncReadWrite{(transaction) in

  if let cloudTransaction = zdc.cloudTransaction(transaction, forLocalUserID: localUserID) {
    do {
      try cloudTransaction.sendMessage(toRecipients: [recipientUserID])
    } catch {}
    // Message is now queued for upload.
  }
}

This creates a node in the treesystem's outbox container, which is now queued to be uploaded. The framework will upload it as soon as it can. (It may have to wait for an Internet connection.) When it's ready, the framework will ask the delegate for the message data. All you need to do is simply return whatever data you'd like to send as a message:

// ZeroDarkCloudDelegate protocol function
func data(forMessage message: ZDCNode, transaction: YapDatabaseReadTransaction) -> ZDCData? {

  // Return message data here.
  // ZDCData supports a promise if you need async.
  // 
  // ZeroDarkCloud framework handles encryption & uploading.
}

The data you provide is automatically encrypted by the framework, and then uploaded to the cloud. First, the message is delivered to your outbox. And then the node gets copied on the server from your outbox to each recipient's inbox.

Once the message has been delivered to a recipient's inbox, you'll receive a notification via the ZeroDarkCloudDelegate:

// ZeroDarkCloudDelegate protocol function
func didSendMessage(_ message: ZDCNode, toRecipient recipient: ZDCUser, transaction: YapDatabaseReadWriteTransaction) {

  // Message delivery notification
}

If the message had multiple recipients, you'll receive a notification for each recipient. The delivery information is readable via the node's deliveries property.

 


Collaborative-style messaging

Collaboration uses a shared branch to allow multiple people to access the same set of data. Thus, whenever a node in the shared branch is created/modified/deleted, all users with access to the shared branch will be notified of the changes.

Collaboration is distinct from messaging in the traditional sense, but in the abstract sense it's the same. In any case:

  • Alice uploads a record to the cloud
  • All of Alice's devices see the record
  • All of Bob's devices see the record

A simple example of this can be seen in the ZeroDarkTodo sample app. This is a simple Todo-style app. It allows users to create one or more List's, such as "Groceries", "Weekend Chores", etc. The user can then add Todo items to each List. And the app allows the user to share a List with other users. So, for example, people living together could collaborate on a Grocery list.

To see how this works, imagine that Alice has a List that she'd like to share with Bob. First, let's take a look at Alice's treesystem:

            (Alice)
            /     \
   (Homework)     (Groceries)
     /  |  \       /  |   |  \
   (A) (B) (C)   (D) (E) (F) (G)

Alice has 2 Lists: "Homework" & "Groceries". Now she decides to share the Groceries list with Bob, so they can collaborate. The idea is to graft Alice's "Groceries" branch into Bob's treesystem:

            (Alice)                          (Bob)
            /     \                          /   \
   (Homework)     (Groceries)<-----------(ptr)     (Weekend Chores)
     /  |  \       /  |   |  \                         /  |  \
   (A) (B) (C)   (D) (E) (F) (G)                     (A) (B) (C)

Once grafted, the "Groceries" list will act like a local list within Bob's treesystem. But the list actually resides within Alice's treesystem. This allows Alice & Bob to share the same list – changes made by either party will be visible to both.

And this collaboration can extend to multiple users. For example, Alice could also invite Carol, who would similarly graft the node into her treesystem.

To learn more about how this works, and how to implement it, see the collaboration article.

 


Signal-style Messaging

A "signal" is a lightweight message. If Alice sends a signal to Bob, then all of Bob's devices will see it. But there's no corresponding overhead on Alice's other devices. That is, the message gets uploaded directly from Alice's device into Bob's inbox. There is no corresponding message in Alice's outbox.

Sending a signal is almost identical to sending a message:

zdc.databaseManager?.rwDatabaseConnection.asyncReadWrite{(transaction) in

  if let cloudTransaction = zdc.cloudTransaction(transaction, forLocalUserID: localUserID) {
    do {
      try cloudTransaction.sendSignal(toRecipient: recipientUserID)
    } catch {}
    // Signal is now queued for upload.
  }
}

Since signal are just outgoing messages, they aren't part of the treesystem. But they are persisted in the database until they've been sent.

When the framework is ready to send the signal, it will ask the delegate for the message data. All you need to do is simply return whatever data you'd like to send as a signal:

// ZeroDarkCloudDelegate protocol function
func data(forMessage message: ZDCNode, transaction: YapDatabaseReadTransaction) -> ZDCData? {

  // Return signal data here.
  // ZDCData supports a promise if you need async.
  // 
  // ZeroDarkCloud framework handles encryption & uploading.

  if (message.isSignal) {
    // ...
  }
}

Once the signal has been delivered to a recipient's inbox, you'll receive a notification via the ZeroDarkCloudDelegate:

// ZeroDarkCloudDelegate protocol function
func didSendMessage(_ message: ZDCNode, toRecipient recipient: ZDCUser, transaction: YapDatabaseReadWriteTransaction) {

  // Message delivery notification
}