Getting Started with the Nexmo Stitch JavaScript SDK

In this getting started guide we'll demonstrate how to build a simple conversation app with IP messaging using the Nexmo Stitch JavaScript SDK.

Concepts

This guide will introduce you to the following concepts.

  • Nexmo Applications - contain configuration for the application that you are building
  • JWTs (JSON Web Tokens  ) - the Stitch API uses JWTs for authentication. JWTs contain all the information the Nexmo platform needs to authenticate requests. JWTs also contain information such as the associated Applications, Users and permissions.
  • Users - users who are associated with the Nexmo Application. It's expected that Users will have a one-to-one mapping with your own authentication system.
  • Conversations - A thread of conversation between two or more Users.
  • Members - Users that are part of a conversation.

Before you begin

  • Ensure you have Node.JS  installed
  • Create a free Nexmo account - signup 
  • Install the Nexmo CLI:

    $ npm install -g nexmo-cli@beta
    

    Setup the CLI to use your Nexmo API Key and API Secret. You can get these from the setting page  in the Nexmo Dashboard.

    $ nexmo setup api_key api_secret
    

1 - Setup

Note: The steps within this section can all be done dynamically via server-side logic. But in order to get the client-side functionality we're going to manually run through setup.

1.1 - Create a Nexmo Application

Create a Nexmo application within the Nexmo platform to use within this guide.

$ nexmo app:create "My Stitch App" https://example.com/answer https://example.com/event --type=rtc --keyfile=private.key

The output of the above command will be something like this:

Application created: aaaaaaaa-bbbb-cccc-dddd-0123456789ab
No existing config found. Writing to new file.
Credentials written to /path/to/your/local/folder/.nexmo-app
Private Key saved to: private.key

The first item is the Application ID which you should take a note of. We'll refer to this as YOUR_APP_ID later. The last value is a private key location. The private key is used to generate JWTs that are used to authenticate your interactions with Nexmo.

1.2 - Create a Conversation

Create a conversation within the application:

$ nexmo conversation:create display_name="Nexmo Chat"

The output of the above command will be something like this:

Conversation created: CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

That is the Conversation ID. Take a note of it as this is the unique identifier for the conversation that has been created. We'll refer to this as YOUR_CONVERSATION_ID later.

1.3 - Create a User

Create a user who will participate within the conversation:

$  nexmo user:create name="jamie"

The output of the above command will be something like this:

User created: USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

That is the User ID. Take a note of it as this is the unique identifier for the user that has been created. We'll refer to this as YOUR_USER_ID later.

1.4 - Add the User to the Conversation

Finally, let's add the user to the conversation that we created. Remember to replace YOUR_CONVERSATION_ID and YOUR_USER_ID values:

$ nexmo member:add YOUR_CONVERSATION_ID action=join channel='{"type":"app"}' user_id=YOUR_USER_ID

The output of this command will confirm that the user has been added to the "Nexmo Chat" conversation.

Member added: MEM-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

You can also check this by running the following request, replacing YOUR_CONVERSATION_ID:

$ nexmo member:list YOUR_CONVERSATION_ID -v

Where you should see an output similar to the following:

name                                     | user_id                                  | user_name | state  
---------------------------------------------------------------------------------------------------------
MEM-aaaaaaaa-bbbb-cccc-dddd-0123456789ab | USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab | jamie     | JOINED

1.5 - Generate a User JWT

Generate a JWT for the user and take a note of it. Remember to change the YOUR_APP_ID value in the command:

$ USER_JWT="$(nexmo jwt:generate ./private.key sub=jamie exp=$(($(date +%s)+86400)) acl='{"paths":{"/v1/users/**":{},"/v1/conversations/**":{},"/v1/sessions/**":{},"/v1/devices/**":{},"/v1/image/**":{},"/v3/media/**":{},"/v1/applications/**":{},"/v1/push/**":{},"/v1/knocking/**":{}}}' application_id=YOUR_APP_ID)"

Note: The above command saves the generated JWT to a USER_JWT constant. It also sets the expiry of the JWT to one day from now.

You can see the JWT for the user by running the following:

$ echo $USER_JWT

2 - Create the JavaScript App

With the basic setup in place we can now focus on the client-side application.

2.1 - An HTML Page with a Basic UI

Create an index.html page and add a very basic UI for the conversation functionality.

The UI contains:

  • A simple login area. We'll be stubbing out a fake login process, but in a real application it would be expected for you to integrate with your chosen login system.
  • A list of messages. All the messages will be output to this area.
  • An input area. We'll use this to send a new message
<!DOCTYPE html>
<html>
<head>
  <style>
    #login, #messages {
      width: 80%;
      height: 300px;
    }

    #messages {
      display: none;
    }
  </style>
</head>
<body>

  <form id="login">
    <h1>Login</h1>
    <input type="text" name="username" value="">
    <input type="submit" value="Login" />
  </form>

  <section id="messages">
    <h1>Messages</h1>

    <div id="messageFeed"></div>

    <textarea id="messageTextarea"></textarea>
    <br>
    <button id="send">Send</button>
  </section>

  <script>
    // Your code will go here
  </script>

</body>
</html>

2.2 - Add the Nexmo Stitch JS SDK

Install the Nexmo Stitch JS SDK

$ npm install nexmo-stitch

Include the Nexmo Stitch JS SDK in the <head>

<script src="./node_modules/nexmo-stitch/dist/conversationClient.js"></script>

2.3 - Stubbed Out Login

Next, let's stub out the login workflow.

Define a constant with a value of the User JWT that was created earlier and set the value to the USER_JWT that was generated earlier. Create a YOUR_CONVERSATION_ID with the value of the Conversation ID that was created earlier to indicate the conversation we're going to be using.

Lastly we'll create a class called ChatApp that creates some instance variables selecting our HTML elements for use later, an error logging method, an event logging method and stub out the functions we'll be creating later.

<script>
const USER_JWT = 'YOUR USER JWT';
const YOUR_CONVERSATION_ID = 'YOUR CONVERSATION ID';

class ChatApp {
  constructor() {
    this.messageTextarea = document.getElementById('messageTextarea')
    this.messageFeed = document.getElementById('messageFeed')
    this.sendButton = document.getElementById('send')
    this.loginForm = document.getElementById('login')
    this.setupUserEvents()
  }

  errorLogger(error) {
      console.log(error)
  }

  eventLogger(event) {
      return () => {
          console.log("'%s' event was sent", event)
      }
  }

  authenticate() { // TODO }

  setupConversationEvents(conversation) { // TODO }

  joinConversation(userToken) { // TODO }

  setupUserEvents() { // TODO }
}

new ChatApp()
</script>

Let's fill in the authenticate function. For now, stub it out to always return the USER_JWT value. This is where you would normally use the users session to authenticate the user and return their JWT.

authenticate() {
  // Your authentication logic would go here.
  return USER_JWT
}

Within setupUserEvents we'll bind to submit events on the form. When this form is submitted then call authenticate to get the user token. Finally, hide the login, show the message elements and call joinConversation, passing the user token.

setupUserEvents() {
  this.loginForm.addEventListener('submit', (event) => {
    event.preventDefault()
    const userToken = this.authenticate()
    if (userToken) {
      document.getElementById('messages').style.display = 'block'
      document.getElementById('login').style.display = 'none'
      this.joinConversation(userToken)
    }
  })
}

2.4 - Connect and Login to Nexmo

Within the joinConversation function, create an instance of the ConversationClient and login the current user in using the User JWT.

joinConversation(userToken) {
  new ConversationClient({ debug: false })
    .login(userToken)
    .then(app => console.log('*** Logged into app', app))
    .catch(this.errorLogger)
}

2.5 - Accessing the Conversation Object

The next step is to have a user to retrieve the Conversation that was created. The login method returns a promise with the app. A user can be a member of many conversations you can call app.getConversations() to get them all. In this case we know the conversation you want so we'll request it with app.getConversation(YOUR_CONVERSATION_ID).

joinConversation(userToken) {
  new ConversationClient({ debug: false })
    .login(userToken)
    .then(app => {
        console.log('*** Logged into app', app)
        return app.getConversation(YOUR_CONVERSATION_ID)
    })
    .catch(this.errorLogger)
}

2.6 - Receiving and Sending text Events

Once we have found the conversation let's pass it to setupConversationEvents. We then want to listen for text event on the conversation and show them in the UI.

setupConversationEvents(conversation) {
  this.conversation = conversation
  console.log('*** Conversation Retrieved', conversation)
  console.log('*** Conversation Member', conversation.me)

  // Bind to events on the conversation
  conversation.on('text', (sender, message) => {
    console.log('*** Message received', sender, message)
    const date = new Date(Date.parse(message.timestamp))
    const text = `${sender.user.name} @ ${date}: <b>${message.body.text}</b><br>`
    this.messageFeed.innerHTML = text + this.messageFeed.innerHTML
  })
}

joinConversation(userToken) {
  new ConversationClient({ debug: false })
    .login(userToken)
    .then(app => {
        console.log('*** Logged into app', app)
        return app.getConversation(YOUR_CONVERSATION_ID)
    })
    .then(this.setupConversationEvents.bind(this))
    .catch(this.errorLogger)
}

Finally, we'll add another event listener in the setupUserEvents function. When the user clicks the send button in the UI send whatever text has been placed in the textarea. This is achieved by calling sendText on the conversation reference we created in setupConversationEvents.

setupUserEvents() {
  this.sendButton.addEventListener('click', () => {
    this.conversation.sendText(this.messageTextarea.value).then(() => {
        this.eventLogger('text')()
        this.messageTextarea.value = ''
    }).catch(this.errorLogger)
  })

  this.loginForm.addEventListener('submit', (event) => {
    event.preventDefault()
    const userToken = this.authenticate()
    if (userToken) {
      document.getElementById('messages').style.display = 'block'
      document.getElementById('login').style.display = 'none'
      this.joinConversation(userToken)
    }
  })
}

That's it! Your page should now look something like this  .

Run index.html in two side-by-side browser windows to see the conversation take place.

Where next?

Getting Started with the Nexmo Stitch Android SDK

In this getting started guide we'll demonstrate how to build a simple conversation app with IP messaging using the Nexmo Stitch Android SDK.

Concepts

This guide will introduce you to the following concepts.

  • Nexmo Applications - contain configuration for the application that you are building
  • JWTs (JSON Web Tokens  ) - the Stitch API uses JWTs for authentication. JWTs contain all the information the Nexmo platform needs to authenticate requests. JWTs also contain information such as the associated Applications, Users and permissions.
  • Users - users who are associated with the Nexmo Application. It's expected that Users will have a one-to-one mapping with your own authentication system.
  • Conversations - A thread of conversation between two or more Users.
  • Members - Users that are part of a conversation.

Before you begin

  • Ensure you have Node.JS and NPM installed (you'll need it for the CLI)
  • Ensure you have Android Studio installed
  • Create a free Nexmo account - signup 
  • Install the Nexmo CLI:

    $ npm install -g nexmo-cli@beta
    

    Setup the CLI to use your Nexmo API Key and API Secret. You can get these from the setting page  in the Nexmo Dashboard.

    $ nexmo setup api_key api_secret
    

1 - Setup

Note: The steps within this section can all be done dynamically via server-side logic. But in order to get the client-side functionality we're going to manually run through setup.

1.1 - Create a Nexmo application

Create an application within the Nexmo platform.

$ nexmo app:create "Stitch Android App" http://example.com/answer http://example.com/event --type=rtc --keyfile=private.key

Nexmo Applications contain configuration for the application that you are building. The output of the above command will be something like this:

Application created: aaaaaaaa-bbbb-cccc-dddd-0123456789ab
No existing config found. Writing to new file.
Credentials written to /path/to/your/local/folder/.nexmo-app
Private Key saved to: private.key

The first item is the Application ID and the second is a private key that is used generate JWTs that are used to authenticate your interactions with Nexmo. You should take a note of it. We'll refer to this as YOUR_APP_ID later.

1.2 - Create a Conversation

Create a conversation within the application:

$ nexmo conversation:create display_name="Nexmo Chat"

The output of the above command will be something like this:

Conversation created: CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

That is the Conversation ID. Take a note of it as this is the unique identifier for the conversation that has been created. We'll refer to this as YOUR_CONVERSATION_ID later.

1.3 - Create a User

Create a user who will participate within the conversation.

$ nexmo user:create name="jamie"

The output will look as follows:

User created: USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

Take a note of the id attribute as this is the unique identifier for the user that has been created. We'll refer to this as YOUR_USER_ID later.

1.4 - Add the User to the Conversation

Finally, let's add the user to the conversation that we created. Remember to replace YOUR_CONVERSATION_ID and YOUR_USER_ID values.

$ nexmo member:add YOUR_CONVERSATION_ID action=join channel='{"type":"app"}' user_id=YOUR_USER_ID

The output of this command will confirm that the user has been added to the "Nexmo Chat" conversation.

Member added: MEM-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

You can also check this by running the following request, replacing YOUR_CONVERSATION_ID:

$ nexmo member:list YOUR_CONVERSATION_ID -v

Where you should see a response similar to the following:

name                                     | user_id                                  | user_name | state  
---------------------------------------------------------------------------------------------------------
MEM-aaaaaaaa-bbbb-cccc-dddd-0123456789ab | USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab | jamie     | JOINED

1.5 - Generate a User JWT

Generate a JWT for the user and take a note of it. Remember to change the YOUR_APP_ID value in the command.

$ USER_JWT="$(nexmo jwt:generate ./private.key sub=jamie exp=$(($(date +%s)+86400)) acl='{"paths":{"/v1/users/**":{},"/v1/conversations/**":{},"/v1/sessions/**":{},"/v1/devices/**":{},"/v1/image/**":{},"/v3/media/**":{},"/v1/applications/**":{},"/v1/push/**":{},"/v1/knocking/**":{}}}' application_id=YOUR_APP_ID)"

Note: The above command saves the generated JWT to a USER_JWT variable. It also sets the expiry of the JWT to one day from now.

You can see the JWT for the user by running the following:

$ echo $USER_JWT

2 - Create the Android App

With the basic setup in place we can now focus on the client-side application

2.1 Start a new project and add the Nexmo Stitch SDK

Open Android Studio and start a new project. We'll name it "Stitch Android Quickstart 1". The minimum SDK will be set to API 19. We can start with an empty activity named "Login Activity".

In the build.gradle file we'll add the Nexmo Stitch Android SDK and the WebRTC dependency.

//app/build.gradle
dependencies {
...
  compile 'com.nexmo:stitch:1.7.1'
  compile 'com.android.support:appcompat-v7:25.3.1'
...
}

//build.gradle
allprojects {
    repositories {
        ...
        //WebRTC dependency for Stitch
        maven { url 'https://artifactory.ess-dev.com/artifactory/gradle-dev' }
    }
}

Then sync your project.

2.2 Add ConversationClient to your app

Before we change our activity, we're going to set up a custom application to share a reference to the ConversationClient across activities.

// ConversationClientApplication.java
public class ConversationClientApplication extends Application {
    private ConversationClient conversationClient;

    @Override
    public void onCreate() {
        super.onCreate();
        this.conversationClient = new ConversationClient.ConversationClientBuilder().context(this).build();
    }

    public ConversationClient getConversationClient() {
        return this.conversationClient;
    }
}

Make sure you also add android:name=".ConversationClientApplication" to the application tag in your AndroidManifest.xml

<!--AndroidManifest.xml-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.nexmo.androidquickstart1">

    <application
        ...
        android:name=".ConversationClientApplication">
    </application>

</manifest>

2.3 Creating the login layout

We're going to create a simple layout for the first activity in our app. There will be buttons for the user to log in and start chatting.

<!--activity_login.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/login_text"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Welcome to Awesome Chat. Login to continue" />

    <LinearLayout
        android:id="@+id/login_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Login"/>

        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>


        <Button
            android:id="@+id/chat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Chat"/>

    </LinearLayout>

</LinearLayout>

2.3 - Create the LoginActivity

We're creating an instance of ConversationClient and saving it as a member variable in the activity.

We also need to wire up the buttons in LoginActivity.java Don't forget to replace USER_JWT with the JWT generated from the Nexmo CLI in step 1.5 and CONVERSATION_ID with the id generated in step 1.2

//LoginActivity.java
public class LoginActivity extends AppCompatActivity {
    private final String TAG = LoginActivity.this.getClass().getSimpleName();
    private String CONVERSATION_ID = YOUR_CONVERSATION_ID;
    private String USER_JWT = YOUR_USER_JWT;

    private ConversationClient conversationClient;
    private TextView loginTxt;
    private Button loginBtn;
    private Button chatBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        ConversationClientApplication application = (ConversationClientApplication) getApplication();
        conversationClient = application.getConversationClient();

        loginTxt = (TextView) findViewById(R.id.login_text);
        loginBtn = (Button) findViewById(R.id.login);
        chatBtn = (Button) findViewById(R.id.chat);

        loginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login();
            }
        });
        chatBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                goToChatActivity();
            }
        });
    }

    private void logAndShow(final String message) {
        Log.d(TAG, message);
        Toast.makeText(LoginActivity.this, message, Toast.LENGTH_SHORT).show();
    }
}

2.4 Stubbed Out Login

Next, let's stub out the login workflow.

Create an authenticate function that takes a username. For now, stub it out to always return the USER_JWT value. Also create a login function that takes a userToken (a JWT).

//LoginActivity.java
private String authenticate() {
    return USER_JWT;
}

private void login() {
    loginTxt.setText("Logging in...");

    String userToken = authenticate();
    conversationClient.login(userToken, new RequestHandler<User>() {
        @Override
        public void onSuccess(User user) {
            showLoginSuccess(user);
        }

        @Override
        public void onError(NexmoAPIError apiError) {
            logAndShow("Login Error: " + apiError.getMessage());
        }
    });
}

private void showLoginSuccess(final User user) {
    loginTxt.setText("Logged in as " + user.getName() + "\nStart a new conversation");
}


To log in a user, you simply need to call login on the conversationClient passing in the user JWT and a RequestHandler<User> as arguments.

The RequestHandler<User> gives you two callbacks, onSuccess when a user has succeeded logging in and onError when there was an error.

After the user logs in, they'll press the "Chat" button which will take them to the ChatActivity and let them begin chatting in the conversation we've already created.

2.5 Navigate to ChatActivity

When we construct the intent for ChatActivity we'll pass the conversation's ID so that the new activity can look up which conversation to join. Remember that CONVERSATION_ID comes from the id generated in step 1.3.

//LoginActivity.java
private void goToChatActivity() {
    Intent intent = new Intent(LoginActivity.this, ChatActivity.class);
    intent.putExtra("CONVERSATION-ID", CONVERSATION_ID);
    startActivity(intent);
}

2.6 Create the Chat layout

We'll make a ChatActivity with this as the layout

<!--activity_chat.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/chat_txt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                tools:text="Here's a chat \nThere's a chat \nEverywhere's a chat-chat"/>

        </LinearLayout>

    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom">

        <EditText
            android:id="@+id/msg_edit_txt"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:imeOptions="actionSend"
            android:hint="Type a message"/>

        <Button
            android:id="@+id/send_msg_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send"/>

    </LinearLayout>

</LinearLayout>

2.7 Create the ChatActivity

Like last time we'll wire up the views in ChatActivity.java We also need to grab the conversationId from the incoming intent so we can look up the appropriate conversation.

//ChatActivity.java
public class ChatActivity extends AppCompatActivity {
  private final String TAG = ChatActivity.this.getClass().getSimpleName();

  private TextView chatTxt;
  private EditText msgEditTxt;
  private Button sendMsgBtn;

  private ConversationClient conversationClient;
  private Conversation conversation;
  private SubscriptionList subscriptions = new SubscriptionList();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_chat);

      chatTxt = (TextView) findViewById(R.id.chat_txt);
      msgEditTxt = (EditText) findViewById(R.id.msg_edit_txt);
      sendMsgBtn = (Button) findViewById(R.id.send_msg_btn);
      sendMsgBtn.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              sendMessage();
          }
      });

      ConversationClientApplication application = (ConversationClientApplication) getApplication();
      conversationClient = application.getConversationClient();

      Intent intent = getIntent();
      String conversationId = intent.getStringExtra("CONVERSATION-ID");
      conversation = conversationClient.getConversation(conversationId);
  }

    private void logAndShow(final String message) {
        Log.d(TAG, message);
        Toast.makeText(ChatActivity.this, message, Toast.LENGTH_SHORT).show();
    }
}

2.8 - Sending text Events

To send a message we simply need to call sendText on our instance of Conversation conversation. sendText takes two arguments, a String message, and an EventSendListener In the EventSendListener we'll get two call backs: onSuccess() and onError(). If there's an error we'll just show an error in the logs and in a toast. We'll just log out the message in the onSuccess() callback since we'll handle messages as they're received instead of as they're sent. You might notice that I'm checking the type of the message before I log it out. That's because a Message can be Text or an Image. For now we'll just worry about Text.

//ChatActivity.java
private void sendMessage() {
    conversation.sendText(msgEditTxt.getText().toString(), new RequestHandler<Event>() {
        @Override
        public void onSuccess(Event event) {
            if (event.getType().equals(EventType.TEXT)) {
                Log.d(TAG, "onSent: " + ((Text) event).getText());
            }
        }

        @Override
        public void onError(NexmoAPIError apiError) {
            logAndShow("Error sending message: " + apiError.getMessage());
        }
    });
}

2.9 - Receiving text Events

We want to know when text messages are being received so we need to add a ResultListener<Event> to the messageEvent() on the conversation. We can do this like so:

//ChatActivity.java
private void addListener() {
    conversation.messageEvent().add(new ResultListener<Event>() {
        @Override
        public void onSuccess(Event message) {
            showMessage(message);
        }
    }).addTo(subscriptions);
}

private void showMessage(final Event message) {
    if (message.getType().equals(EventType.TEXT)) {
        Text text = (Text) message;
        msgEditTxt.setText(null);
        final String prevText = chatTxt.getText().toString();
        chatTxt.setText(prevText + "\n" + text.getText());
    }
}

Adding a new ResultListener<Event> to a conversation.messageEvent() allows us to add a callback when a message is received. There's only one callback onSuccess that gets fired whenever an Event is received. An Event can be some text or an image. When onSuccess is fired we'll call our showMessage() method.

You'll also notice this bit of code .addTo(subscriptions) We'll talk more about that in the next section.

Before we handle the message we need to ensure that it's a Text message. As stated earlier, Events can also be an image. We won't implement images in this guide, but it's good practice for the future. showMessage() removes the text from the msgEditTxt and appends the text from the message to our chatTxt along with any previous messages.

2.10 - Adding and removing listeners

Finally, we need to call addListener() in order to send and receive messages. We should also unsubscribe from all of the ResultListeners when our Activity is winding down.

//ChatActivity.java
@Override
protected void onResume() {
    super.onResume();
    addListener();
}

@Override
protected void onPause() {
    super.onPause();
    subscriptions.unsubscribeAll();
}

When we created our ChatActivity we added a member variable to our activity with SubscriptionList subscriptions = new SubscriptionList(); A SubscriptionList is a utility list that the library provides to make it easier to manage subscriptions within the app lifecycle. Basically, when we add a new ResultListener we should call .addTo(subscriptions) on that ResultListener so that we can call subscriptions.unsubscribeAll(); when our activity winds down. We do this to minimize memory leaks.

Try it out

After this you should be able to run the app and send messages to a conversation like so:

Hello world!

Where next?

Try out Quickstart 2

Getting Started with the Nexmo Stitch iOS SDK

In this getting started guide we'll demonstrate how to build a simple conversation app with IP messaging using the Nexmo Stitch iOS SDK.

Concepts

This guide will introduce you to the following concepts.

  • Nexmo Applications - contain configuration for the application that you are building
  • JWTs (JSON Web Tokens  ) - the Stitch API uses JWTs for authentication. JWTs contain all the information the Nexmo platform needs to authenticate requests. JWTs also contain information such as the associated Applications, Users and permissions. It helps you as well as Nexmo facilitate the retention & analysis of metadata for future AI implementations.
  • Users - users who are associated with the Nexmo Application. It's expected that Users will have a one-to-one mapping with your own authentication system.
  • Conversations - A thread of conversation between two or more Users.
  • Members - Users that are part of a conversation.

Before you begin

  • Ensure you have Node.JS and NPM installed (you'll need it for the Nexmo CLI)
  • Ensure you have Xcode installed
  • Create a free Nexmo account - signup 
  • Install the Nexmo CLI:

    $ npm install -g nexmo-cli@beta
    

    Setup the CLI to use your Nexmo API Key and API Secret. You can get these from the setting page  in the Nexmo Dashboard.

    $ nexmo setup api_key api_secret
    

1 - Setup

Note: The steps within this section can all be done dynamically via server-side logic. But in order to get the client-side functionality we're going to manually run through setup.

1.1 - Create a Nexmo application

Create an application within the Nexmo platform.

$ nexmo app:create "Stitch iOS App" http://example.com/answer http://example.com/event --type=rtc --keyfile=private.key

Nexmo Applications contain configuration for the application that you are building. The output of the above command will be something like this:

Application created: aaaaaaaa-bbbb-cccc-dddd-0123456789ab
No existing config found. Writing to new file.
Credentials written to /path/to/your/local/folder/.nexmo-app
Private Key saved to: private.key

The first item is the Application ID and the second is a private key that is used generate JWTs that are used to authenticate your interactions with Nexmo. You should take a note of it. We'll refer to this as YOUR_APP_ID later.

1.2 - Create a Conversation

Create a conversation within the application:

$ nexmo conversation:create display_name="Nexmo Chat"

The output of the above command will be something like this:

Conversation created: CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

That is the Conversation ID. Take a note of it as this is the unique identifier for the conversation that has been created. We'll refer to this as YOUR_CONVERSATION_ID later.

1.3 - Create a User

Create a user who will participate within the conversation.

$ nexmo user:create name="jamie"

The output will look as follows:

User created: USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

Take a note of the id attribute as this is the unique identifier for the user that has been created. We'll refer to this as YOUR_USER_ID later.

1.4 - Add the User to the Conversation

Finally, let's add the user to the conversation that we created. Remember to replace YOUR_CONVERSATION_ID and YOUR_USER_ID values.

$ nexmo member:add YOUR_CONVERSATION_ID action=join channel='{"type":"app"}' user_id=YOUR_USER_ID

The output of this command will confirm that the user has been added to the "Nexmo Chat" conversation.

Member added: MEM-aaaaaaaa-bbbb-cccc-dddd-0123456789ab

You can also check this by running the following request, replacing YOUR_CONVERSATION_ID:

$ nexmo member:list YOUR_CONVERSATION_ID -v

Where you should see a response similar to the following:

name                                     | user_id                                  | user_name | state  
---------------------------------------------------------------------------------------------------------
MEM-aaaaaaaa-bbbb-cccc-dddd-0123456789ab | USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab | jamie     | JOINED

1.5 - Generate a User JWT

Generate a JWT for the user and take a note of it. Remember to change the YOUR_APP_ID value in the command.

$ USER_JWT="$(nexmo jwt:generate ./private.key sub=jamie exp=$(($(date +%s)+86400)) acl='{"paths":{"/v1/users/**":{},"/v1/conversations/**":{},"/v1/sessions/**":{},"/v1/devices/**":{},"/v1/image/**":{},"/v3/media/**":{},"/v1/applications/**":{},"/v1/push/**":{},"/v1/knocking/**":{}}}' application_id=YOUR_APP_ID)"

Note: The above command saves the generated JWT to a USER_JWT variable. It also sets the expiry of the JWT to one day from now.

You can see the JWT for the user by running the following:

$ echo $USER_JWT

1.6 The Nexmo Stitch API Dashboard

If you would like to double check any of the JWT credentials, navigate to your-applications  where you can see a table with three entries respectively entitled "Name", "Id", or "Security settings". Under the menu options for "Edit" next to "Delete", you can take a peak at the details of the applications such as "Application name", "Application Id", etc...

2 - Create the iOS App

With the basic setup in place we can now focus on the client-side application

2.1 Start a new project

Open Xcode and start a new project. We'll name it "QuickStartOne".

2.2 Adding the Nexmo Stitch iOS SDK to Cocoapods

Navigate to the project's root directory in the Terminal. Run: pod init. Open the file entitled PodFile. Configure its specifications accordingly:

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

source "https://github.com/Nexmo/PodSpec.git"
source 'git@github.com:CocoaPods/Specs.git'

target 'QuickStartOne' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!
  pod "NexmoConversation", :git => "https://github.com/nexmo/conversation-ios-sdk.git", :branch => "master" # development
end

2.3 Adding ViewControllers & .storyboard files

Let's add a few view controllers. Start by adding a custom subclass of UIViewController from a CocoaTouch file named LoginViewController, which we will use for creating the login functionality, and another custom subclass of UIViewController from a CocoaTouch file named ChatViewController, which we will use for creating the chat functionality. Add two new scenes to Main.storyboard, assigning each to one of the added custom subclasses of UIViewController respectively.

2.4 Creating the login layout

Let's layout the login functionality. Set constraints on the top & leading attributes of an instance of UIButton with a constant HxW at 71x94 to the top of the Bottom Layout Guide + 20 and the leading attribute of view + 16. This is our login button. Reverse leading to trailing for another instance of UIButton with the same constraints. This our chat button. Set the text on these instances accordingly. Add a status label centered horizontally & vertically. Finally, embedd this scene into a navigation controller. Control drag from the chat button to scene assigned to the chat controller, naming the segue chatView.

2.5 - Create the Login Functionality

Below UIKit let's import the NexmoConversation. Next we setup a custom instance of the ConversationClient and saving it as a member variable in the view controller.

    /// Nexmo Conversation client
    let client: ConversationClient = {
        return ConversationClient.instance
    }()

We also need to wire up the buttons in LoginViewController.swift Don't forget to replace USER_JWT with the JWT generated from the Nexmo CLI in step 1.5.

    // status label
    @IBOutlet weak var statusLbl: UILabel!

    // login button
    @IBAction func loginBtn(_ sender: Any) {

        print("DEMO - login button pressed.")

        let token = Authenticate.userJWT

        print("DEMO - login called on client.")

        client.login(with: token).subscribe(onSuccess: {

            print("DEMO - login susbscribing with token.")
            self.statusLbl.isEnabled = true
            self.statusLbl.text = "Logged in"

            if let user = self.client.account.user {
                print("DEMO - login successful and here is our \(user)")
            } // insert activity indicator to track subscription

        }, onError: { [weak self] error in
            self?.statusLbl.isHidden = false

            print(error.localizedDescription)

            // remove to a function
            let reason: String = {
                switch error {
                case LoginResult.failed: return "failed"
                case LoginResult.invalidToken: return "invalid token"
                case LoginResult.sessionInvalid: return "session invalid"
                case LoginResult.expiredToken: return "expired token"
                case LoginResult.success: return "success"
                default: return "unknown"
                }
            }()

            print("DEMO - login unsuccessful with \(reason)")

        }).addDisposableTo(client.disposeBag) // Rx does not maintain a memory reference; to make sure that reference is still in place; keep a reference of this object while I do an operation.
    }

    // chat button
    @IBAction func chatBtn(_ sender: Any) {

        let aConversation: String = "aConversation"
        _ = client.conversation.new(aConversation, withJoin: true).subscribe(onError: { error in

            print(error)

            guard self.client.account.user != nil else {

                let alert = UIAlertController(title: "LOGIN", message: "The `.user` property on self.client.account is nil", preferredStyle: .alert)

                let alertAction = UIAlertAction(title: "OK", style: .default, handler: nil)

                alert.addAction(alertAction)

                self.present(alert, animated: true, completion: nil)

                return print("DEMO - chat self.client.account.user is nil");

            }

            print("DEMO - chat creation unsuccessful with \(error.localizedDescription)")

        })

        performSegue(withIdentifier: "chatView", sender: nil)
    }

2.6 Stubbed Out Login

Next, let's stub out the login workflow.

Create an authenticate struct with a member set as userJWT. For now, stub it out to always return the vaue for USER_JWT.

// a stub for holding the value for private.key
struct Authenticate {

    static let userJWT = ""

}

After the user logs in, they'll press the "Chat" button which will take them to the ChatViewController and let them begin chatting in the conversation we've already created.

2.5 Navigate to ChatViewController

As we mentioned above, creating a conversation results from a call to the the new() method. In the absence of a server we’ll ‘simulate’ the creation of a conversation within the app when the user clicks the chatBtn.

When we construct the segue for ChatViewController, we pass the first conversation so that the new controller. Remember that the CONVERSATION_ID comes from the id generated in step 1.2.

    // prepare(for segue:)
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // setting up a segue
        let chatVC = segue.destination as? ChatController

        // passing a reference to the conversation
        chatVC?.conversation = client.conversation.conversations.first


    }

2.6 Create the Chat layout

We'll make a ChatActivity with this as the layout. Add an instance of UITextView, UITextField, & UIButton.Set the constraints on UITextView with setting its constraints: .trailing = trailingMargin, .leading = Text Field.leading, .top = Top Layout Guide.bottom, .bottom + 15 = Text Field.top. Set the leading attribute on the Text Field to = leadingMargin and its .bottom attribute + 20 to Bottom Layout Guide's top attribute. Set the Button's .trailing to trailingMargin + 12 and its .bottom attribute + 20 to the Bottom Layout Guide's .top attribute.

2.7 Create the ChatActivity

Like last time we'll wire up the views in ChatViewController.swift We also need to grab the reference to conversation from the incoming view controller.


import UIKit
import NexmoConversation

class ChatController: UIViewController {

    // conversation for passing client
    var conversation: Conversation?

    // textView for displaying chat
    @IBOutlet weak var textView: UITextView!

    // textField for capturing text
    @IBOutlet weak var textField: UITextField!

}

2.8 - Sending text Events

To send a message we simply need to call send() on our instance of conversation. send() takes one argument, a String message.

    // sendBtn for sending text
    @IBAction func sendBtn(_ sender: Any) {

        do {
            // send method
            try conversation?.send(textField.text!)

        } catch let error {
            print(error)
        }

    }

2.9 - Receiving text Events

In viewDidLoad() we want to add a handler for handling new events like the TextEvents we create when we press the send button. We can do this like so:

        // a handler for updating the textView with TextEvents
        conversation?.events.newEventReceived.addHandler { event in
            guard let event = event as? TextEvent, event.isCurrentlyBeingSent == false else { return }
            guard let text = event.text else { return }

            self.textView.insertText("\n \n \(text) \n \n")
        }

Try it out

After this you should be able to run the app and send messages.