Your First Feature: Messaging Friends

Let’s dive into implementing a new feature for our social network app. From that we’ll get a better idea of how to build DAML applications using our template.

Right now our app allows us to add friends to our network, but we can’t communicate with them! Let’s fix that by adding a private messaging feature. This should allow a user to send messages to a chosen friend, and see all messages that have been sent to them. Of course we must make sure that no one can see messages that were not sent to them. We will see that DAML lets us implement this in a direct and intuitive way.

There are two parts to building the messaging feature: the DAML code and the UI. Let’s start with adding to the DAML code, on which we will base our UI changes.

DAML Changes

The DAML code defines the workflow of the application. This means: what interactions between users (or parties) are permitted by the system? In the context of our feature, the question is: when is a user allowed to message another user?

The approach we’ll take is: a user Bob can message another user Alice if Alice has added Bob as a friend. Remember that friendships are single-directional in our app. So Alice adding Bob as a friend means that she gives permission (or authority) for Bob to send her a message.

In DAML this workflow is represented as a new choice on the User contract.

    nonconsuming choice SendMessage: ContractId Message with
        sender: Party
        content: Text
      controller sender
      do
        assertMsg "You must be a friend to send a message" (sender `elem` friends)
        create Message with sender, receiver = username, content

Let’s break this down. The choice is nonconsuming because sending a message should not affect the existence of the User contract. By convention, the choice returns the ContractId of the resulting Message contract (which we’ll show next). The parameters to the choice are the sender (the party wishing to talk to the signatory of this User contract) and the message text. The controller clause suggests that it is the sender who can exercise the choice. Finally, the body of the choice makes sure that the sender is a friend of the user and then creates the Message with the sender, receiver and content.

Now let’s see the Message contract template. This is very simple - data and no choices - as well as the signatory declaration.

template Message with
    sender: Party
    receiver: Party
    content: Text
  where
    signatory sender, receiver

Note that we have two signatories on the Message contract: both the sender and receiver. This enforces the fact that the contract creation (and archival) must be authorized by both parties.

Now we’ve specified the workflow of sending messages, let’s integrate the functionality into our app.

TypeScript Code Generation

Remember that we interface with our DAML code from the UI components using the generated TypeScript. Since we have changed our DAML code, we also need to rerun the TypeScript code generator. Let’s do this now by running:

daml build
daml codegen ts .daml/dist/create-daml-app-0.1.0.dar -o daml-ts/src

As the TypeScript code is generated into the separate daml-ts workspace on which the UI depends, we need to rebuild the workspaces from the root directory using:

yarn workspaces run build

We should now have the updated TypeScript code with equivalents of the Message template and SendMessage choice.

Now let’s implement our messaging feature in the UI!

Messaging UI

Our messaging feature has two parts: a form with inputs for selecting friends and composing the message text, and a “feed” of messages that have been sent to you. Both parts will be implemented as React components that render on the main screen.

MessageList Component

The MessageList component is fairly straight-forward: it queries all Message contracts and displays their contents as a list. Here is the code for the entire component.

// Copyright (c) 2020 The DAML Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react'
import { List, ListItem } from 'semantic-ui-react';
import { Message } from '@daml2ts/create-daml-app/lib/create-daml-app-0.1.0/User';
import { useParty, useQuery } from '@daml/react';

/**
 * React component displaying the list of messages for the current user.
 */
const MessageList: React.FC = () => {
  const username = useParty();
  const messagesResult = useQuery(Message, () => ({receiver: username}), []);
  const messages = messagesResult.contracts.map(message => message.payload);

  const showMessage = (message: Message): string => {
    return (message.sender + ": " + message.content);
  }

  return (
    <List relaxed>
      {messages.map(message => <ListItem>{showMessage(message)}</ListItem>)}
    </List>
  );
}

export default MessageList;

The messagesResult tracks the state of Message contracts on the ledger, where we specify no restrictions on the query. We extract the actual message data in messages. Note that for any particular user, the Message query yields exactly the messages that have been either written by or sent to that user. This is due to how we modelled the signatories and observers in the Message template, and means we do not risk a privacy breach coming from the application code.

Message Edit Component

In addition to the feed component, we need a component for composing messages and sending them using the appropriate choice on the User contract.

// Copyright (c) 2020 The DAML Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react'
import { Form, Input, Dropdown, Button } from 'semantic-ui-react';
import { Party } from '@daml/types';
import { User } from '@daml2ts/create-daml-app/lib/create-daml-app-0.1.0/User';
import { useParty, useExerciseByKey } from '@daml/react';

type Props = {
  friends: Party[];
}

/**
 * React component to edit a message to send to a friend.
 */
const MessageEdit: React.FC<Props> = ({friends}) => {
  const sender = useParty();
  const [receiver, setReceiver] = React.useState('');
  const [content, setContent] = React.useState('');
  const [exerciseSendMessage] = useExerciseByKey(User.SendMessage);
  const [isSubmitting, setIsSubmitting] = React.useState(false);

  const sendMessage = async (receiver: string, content: string): Promise<boolean> => {
    try {
      await exerciseSendMessage(receiver, {sender, content});
      return true;
    } catch (error) {
      alert("Error sending message:\n" + JSON.stringify(error));
      return false;
    }
  }

  const submitMessage = async (event?: React.FormEvent) => {
    if (event) {
      event.preventDefault();
    }
    setIsSubmitting(true);
    const success = await sendMessage(receiver, content);
    setIsSubmitting(false);
    if (success) {
      // Keep the receiver selected for follow-on messages
      // but clear the message text.
      setContent('');
    }
  }

  // Options for dropdown menu
  const friendOptions = friends.map(f => ({ key: f, text: f, value: f }));

  return (
    <Form onSubmit={submitMessage}>
      <Dropdown
        fluid
        selection
        placeholder='Select friend'
        options={friendOptions}
        value={receiver}
        onChange={(event) => setReceiver(event.currentTarget.textContent ?? '')}
      />
      <br />
      <Input
        fluid
        transparent
        readOnly={isSubmitting}
        loading={isSubmitting}
        placeholder="Write a message"
        value={content}
        onChange={(event) => setContent(event.currentTarget.value)}
      />
      <br />
      <Button type="submit">Send</Button>
    </Form>
  );
};

export default MessageEdit;

In this component we use React hooks to track the current choice of message receiver and content. The exerciseSendMessage hook gives us a function to exercise the appropriate choice on our User. We wrap this in the sendMessage function to report potential errors to the user, and then the submitMessage function, called when the “Send” button is clicked. The isSubmitting state is used to ensure that message requests are processed one at a time. The result of each send is a new Message contract created on the ledger.

View Component

The MainView component composes the different subcomponents (for our friends, the network and the messaging components above) to build the full app view.

We first import our two new components.

import MessageEdit from './MessageEdit';
import MessageList from './MessageList';

Then we can integrate our new messaging components into the main screen view. In another segment we add the panel including our two new components: the MessageEdit and the MessageList.

            <Segment>
              <Header as='h2'>
                <Icon name='pencil square' />
                <Header.Content>
                  Messages
                  <Header.Subheader>Send a message to a friend</Header.Subheader>
                </Header.Content>
              </Header>
              <MessageEdit
                friends={friends.map(user => user.username)}
              />
            <Divider />
            <MessageList />
            </Segment>

This wraps up the implementation of your first end-to-end DAML feature! Let’s give the new functionality a spin. Follow the instructions in “Running the app” to start up the new app.