Wesh App: Share Contact and Send Message
Tags: protocol, web3, opensource, p2p
Published: 2023-09-05 15:26:34

We continue to use the Wesh API to build more capable apps. In the [previous blog post](https://wesh.network/posts/wesh-app-with-persistent-storage), we made an example app which creates an account using persistent on-disk storage, and we discussed the types of information in Wesh. This includes a Contact group where two user accounts can communicate. In this blog post we will make an example app to share contact and send a message in the Contact group. (This is a longer blog post because we’re building a running app.)

As before, we write an app similar to the [Go tutorial](https://go.dev/doc/tutorial/getting-started). In a terminal enter:

```
cd
mkdir contact
cd contact
go mod init example/contact
```

**The main function**

In your text editor, create a file **_contact.go_** in which to write your code. Paste the following code into your contact.go file.

```
package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"time"

	"berty.tech/weshnet"
	"berty.tech/weshnet/pkg/protocoltypes"
	"github.com/mr-tron/base58"
  )
```
([See the first example app blog post](https://wesh.network/posts/wesh-hello-world-app) for explanation.) To continue the example, paste the following main function.

```
func main() {

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	client1, err := weshnet.NewPersistentServiceClient("data1")
	if err != nil {
		panic(err)
	}
	defer client1.Close()

	// client1 shares contact with client2.
	binaryContact, err := client1.ShareContact(ctx,
		&protocoltypes.ShareContact_Request{})
	if err != nil {
		panic(err)
	}
	fmt.Println(base58.Encode(binaryContact.EncodedContact))

	// client1 receives the contact request from client2.
	request, err := receiveContactRequest(ctx, client1)
	if err != nil {
		panic(err)
	}
	if request == nil {
		fmt.Println("Error: Did not receive the contact request")
		return
	}

	// client1 accepts the contact request from client2.
	_, err = client1.ContactRequestAccept(ctx,
		&protocoltypes.ContactRequestAccept_Request{
			ContactPK: request.ContactPK,
		})
	if err != nil {
		panic(err)
	}

	// Activate the contact group.
	groupInfo, err := client1.GroupInfo(ctx, &protocoltypes.GroupInfo_Request{
		ContactPK: request.ContactPK,
	})
	if err != nil {
		panic(err)
	}
	_, err = client1.ActivateGroup(ctx, &protocoltypes.ActivateGroup_Request{
		GroupPK: groupInfo.Group.PublicKey,
	})
	if err != nil {
		panic(err)
	}

	// Receive a message from the group.
	message, err := receiveMessage(ctx, client1, groupInfo)
	if err != nil {
		panic(err)
	}
	if message == nil {
		fmt.Print("End of stream without receiving message")
		return
	}

	fmt.Println("client2:", string(message.Message))
}
```
This uses some helper functions which we will define below. As in the previous example, we call **NewPersistentServiceClient("data1")** to create a client with persistent storage on-disk. We name the folder “data1” because this is client1. It will communicate with client2 which we create below.

Next we call the Wesh API function **ShareContact** which is [documented here](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.ShareContact). It returns an encoded byte array with the information that client2 needs to make a contact request. We use **base58.Encode** to make it a shareable string and print it to the console. (The Wesh API leaves it up to the application developer to decide how to encode the byte array. You may use base58, or make a QR code, or a URI, etc.) This string is used by client2, as we will see.

Let’s imagine that client2 has sent the contact request, so we call **receiveContactRequest** which we will define below. It returns request which has the account public key of client2, so we call **ContactRequestAccept** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.ContactRequestAccept) to accept the contact request and create the Contact group.

Now we need to activate this group. We use the same account public key of client2 to identify the Contact group and call GroupInfo [(docs) ](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.GroupInfo)to get the group’s public key, and then call **ActivateGroup** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.ActivateGroup) to activate it.

Finally, let’s imagine that client2 has sent a message to the group, so we call **receiveMessage** which we will define below. This returns the message (or nil if end of stream) which we print to the console.

**Helper functions**

That’s it for main! Now we need to define the helper functions **receiveContactRequest** and **receiveMessage**. These both follow the pattern of subscribing to an event stream and waiting for the desired event type. Paste the following function to the file **contact.go**.

```
func receiveContactRequest(ctx context.Context, client weshnet.ServiceClient) (*protocoltypes.AccountContactRequestIncomingReceived, error) {
	// Get the client's AccountGroupPK from the configuration.
	config, err := client.ServiceGetConfiguration(ctx, &protocoltypes.ServiceGetConfiguration_Request{})
	if err != nil {
		return nil, err
	}

	// Subscribe to metadata events. ("sub" means "subscription".)
	subCtx, subCancel := context.WithCancel(ctx)
	defer subCancel()
	subMetadata, err := client.GroupMetadataList(subCtx, &protocoltypes.GroupMetadataList_Request{
			GroupPK: config.AccountGroupPK,
		})
	if err != nil {
		return nil, err
	}

	for {
		metadata, err := subMetadata.Recv()
		if err == io.EOF || subMetadata.Context().Err() != nil {
			// Not received.
			return nil, nil
		}
		if err != nil {
			return nil, err
		}

		if metadata == nil || metadata.Metadata.EventType !=
				protocoltypes.EventTypeAccountContactRequestIncomingReceived {
			continue
		}

		request := &protocoltypes.AccountContactRequestIncomingReceived{}
		if err = request.Unmarshal(metadata.Event); err != nil {
			return nil, err
		}

		return request, nil
	}
}

```
This function takes the client1 which we created in **main** and calls **ServiceGetConfiguration** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.ServiceGetConfiguration). Instead of getting the configuration’s peer ID as in previous examples, we get the public key of client1’s Account group. This public key is also in the shared contact, and is used by client2 to send a contact request to client1. To receive it, we call **GroupMetadataList **[(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.GroupMetadataList) with the Account group public key.

Most API functions return a data structure, but a few like **GroupMetadataList** return a subscription stream like **subMetadata**. We use a **for** loop and call **subMetadata.Recv()** which blocks until it receives an event (or end of stream). As you build more complex apps, an event loop like this may handle more event types and operations. For now, we just check that the event type is the one we’re waiting for, **EventTypeAccountContactRequestIncomingReceived.**

Now we can use **Unmarshal** to convert the **metadata** event to the specific **AccountContactRequestIncomingReceived** event. (**Unmarshal** is part of the Protobuf interface. For more details, [see the docs)](https://pkg.go.dev/github.com/golang/protobuf/proto#Unmarshal). This is the contact **request** that we return from the function.

Now we need to define **receiveMessage** which waits for the message. Paste the following function to the file **contact.go**. (This one is shorter!)

```
func receiveMessage(ctx context.Context, client weshnet.ServiceClient, groupInfo *protocoltypes.GroupInfo_Reply) (*protocoltypes.GroupMessageEvent, error) {
	// Subscribe to message events.
	subCtx, subCancel := context.WithCancel(ctx)
	defer subCancel()
	subMessages, err := client.GroupMessageList(subCtx, &protocoltypes.GroupMessageList_Request{
		GroupPK: groupInfo.Group.PublicKey,
	})
	if err != nil {
		panic(err)
	}

	// client waits to receive the message.
	for {
		message, err := subMessages.Recv()
		if err == io.EOF {
			// Not received.
			return nil, nil
		}
		if err != nil {
			return nil, err
		}

		return message, nil
	}
}
```
Similar to the previous function, this function takes the client1 which we created in **main**. It also takes the **groupInfo** object of the Contact group. (The **main** function already used this to activate the group.)

As in the previous function, we want to subscribe to events. In the [previous blog post](https://wesh.network/posts/wesh-app-with-persistent-storage), we briefly discussed the difference between the Metadata log and the Message log. A contact request is an event in the Metadata log, so **receiveContactRequest** called **GroupMetadataList**. But now we want to receive a “normal” message when it is added to the Message log.

We call the API method **GroupMessageList** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.GroupMessageList) which returns the subscription stream **subMessages**. We use a for loop and call **subMetadata.Recv()** which blocks until it receives an event (or end of stream). Finally, the function returns **message** which is a **GroupMessageEvent** so that the main function can print the message from client2.

**Client 2**

Wait a moment (you may be thinking). We don’t have the code where client2 sends the message. We will add it to this same file and you will run the app in two terminals. Remember that the code for client1 prints the contact string to the terminal. When we run the app for client2, we’ll add this string as a command-line parameter. At the very beginning of the main function, insert this code:

```
```
if len(os.Args) == 2 {
		doClient2(os.Args[1])
		return
	}
```
Now we can define the function **doClient2** which is called if we run using the contact string. Paste the following function to the file **contact.go** and save it.

func doClient2(encodedContact string) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	client2, err := weshnet.NewPersistentServiceClient("data2")
	if err != nil {
		panic(err)
	}
	defer client2.Close()

	contactBinary, err := base58.Decode(encodedContact)
	if err != nil {
		panic(err)
	}
	contact, err := client2.DecodeContact(ctx,
		&protocoltypes.DecodeContact_Request{
			EncodedContact: contactBinary,
		})
	if err != nil {
		panic(err)
	}

	// Send the contact request.
	_, err = client2.ContactRequestSend(ctx,
		&protocoltypes.ContactRequestSend_Request{
			Contact: contact.Contact,
		})
	if err != nil {
		panic(err)
	}

	// Activate the contact group.
	groupInfo, err := client2.GroupInfo(ctx, &protocoltypes.GroupInfo_Request{
		ContactPK: contact.Contact.PK,
	})
	if err != nil {
		panic(err)
	}
	_, err = client2.ActivateGroup(ctx, &protocoltypes.ActivateGroup_Request{
		GroupPK: groupInfo.Group.PublicKey,
	})
	if err != nil {
		panic(err)
	}

	// Send a message to the contact group.
	_, err = client2.AppMessageSend(ctx, &protocoltypes.AppMessageSend_Request{
		GroupPK: groupInfo.Group.PublicKey,
		Payload: []byte("Hello"),
	})
	if err != nil {
		panic(err)
	}

	fmt.Println("Sending message...")
	time.Sleep(time.Second * 5)
}
```

This function takes the **encodedContact** from the command line. It runs as a separate process, so we need to use **NewPersistentServiceClient** to create a separate client2 with persistent data stored in a separate folder, “data2”.

Next we use **base58.Decode** to recover the encoded byte array with client1’s contact info, and use **DecodeContact** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.DecodeContact) to extract the **contact info**. Now client2 can call **ContactRequestSend** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.ContactRequestSend) to send this to client1 as a contact request. You may be thinking, “client1 just sent this info to client2, so why does client2 need to send it back?” This is part of the secure handshake. client2 needs to make sure that the shared contact really came from client1, and needs to decide if creating a contact is actually desired.

Similar to the code above, client2 needs to activate the Contact group using **GroupInfo **and **ActivateGroup**. (In this case, client2 has client1’s account public key from the shared contact in **contact.Contact.PK.**)

We’re almost done! Client2 calls **AppMessageSend** [(docs)](https://buf.build/berty/weshnet/docs/main:weshnet.protocol.v1#weshnet.protocol.v1.ProtocolService.AppMessageSend) to use the Contact group public key to send a “Hello” message to the Contact group. (For generality, a message is any byte array. In this case we simply store the message string in it.) For efficiency, the **AppMessageSend** function queues the message to be sent and returns immediately. If we exit the application too soon, then the Wesh services won’t have time to actually send the message. Therefore, we sleep this function for 5 seconds so that the service threads can complete.

**Run the app**

That’s all the code! It’s time to run the app. In a terminal enter:

```
go mod tidy
go run .
```
(You only need to do **go mod tidy** the first time.) It should print the contact string from client1, something like **2KqzJQpZ2Y7EDaep6CnceT6ozqy1Ss6qJV8tsN59QSBejfa4TiYjMr8Z9PjHr1D2bYa4EozWudwaWMwB5jXqb5gRLj2bX**. Copy this to the clipboard.

Leave this app running. Now, in a separate terminal we run as client2. cd to the same directory and enter:

```
go run . <contact-string> 
```
where **<contact-string>** is the contact string from client1. It should print **Sending message**.... Now look at the terminal for client1. It should print **client2: Hello.**

You have established Wesh communication! You can use this example app as a basis for more sophisticated Wesh apps. Overall, this simply creates a Contact group and calls **AppMessageSend**. But you may understand that Wesh communicates differently than using a traditional network connection. How does the message actually get from client2 to client1? In the next blog post, we’ll do a theory dive into how Wesh’s asynchronous communication works.