Notifications
How to send and receive real-time messages
In Dart, the AtClient is stored within the AtClientManager. Once an atSign has been onboarded, you will be able to access the AtClientManager for its associated atSign.
AtClientManager
AtClientManager is a singleton model. When AtClientManager.getInstance()
is called, it will get the AtClientManager instance for the last onboarded atSign.
AtClientManager atClientManager = AtClientManager.getInstance();
If you need simultaneous access to multiple atClients, you need to create a new isolate for each additional atClient, and onboard its atSign within the isolate.
An example of this pattern can be found in at_daemon_server.
AtClient
As previously mentioned, the AtClientManager stores the actual AtClient itself. You can retrieve the AtClient
by calling atClientManager.atClient
.
AtClient atClient = atClientManager.atClient;
Monitor
You can subscribe to a stream of notifications like this:
AtClient atClient = AtClientManager.getInstance().atClient;
atClient.notificationService
.subscribe(regex: 'message.$nameSpace@', shouldDecrypt: true)
.listen((notification) {
print("Got a message: ${notification.value}");
});
Notify
You can send (a.k.a. publish) a notification like this:
AtKey messageKey = (AtKey.shared('message', nameSpace)
..sharedWith('@bob')).build();
String message = "Hi bob, how are you?";
await atClient.notificationService.notify(
NotificationParams.forUpdate(messageKey, value: message),
);
C
You can find the full code of this example on our GitHub.
Table of Contents
Introduction
Events is how we send and receive real-time messages in the atProtocol. The client SDK assists in using the atProtocol simply and handles all the complex encryption stuff for you.
notify
is how we send a real-time message to an atSignmonitor
is how we listen for real-time messages
Monitor
1. Include `monitor.h`
#include <atclient/monitor.h>
2. Create a Monitor Context
First, we need to create a separate monitor connection to our atServer. This is similar to creating a atclient context for conducting CRUD operations, but it is important to make a completely separate one so that connection can be clear of any noise and used purely for monitoring for notifications.
atclient monitor_client;
atclient_init(&monitor_client);
3. Call `atclient_monitor_pkam_authenticate`
This is similar to the atclient_pkam_authenticate
that you know and love. It should use the same atserver_host
, atserver_port
, and atclient_atkeys
in your original atclient context because we are connecting to the same atServer that can be authenticated using the same set of cryptographic atKeys.
This function will return an int
for error handling in which a non-zero exit code indicates an error.
if (atclient_monitor_pkam_authenticate(
&monitor_client,
atserver_host,
atserver_port,
&atkeys,
"@jeremy_0") != 0) {
// an error occurred
}
4. Call `atclient_monitor_start`
This will begin the monitoring process of the atclient context that you pass. Since we made a completely different connection, this context will be purely for listening for any notifications.
In this example, we pass .*
for the regex to receive all incoming notifications.
This function will return an int
for error handling in which a non-zero exit code indicates an error.
if(atclient_monitor_start(&monitor_client, ".*") != 0) {
// an error occurred
}
5. Call `atclient_monitor_read`
Calling atclient_monitor_read
will attempt to read the network buffer for any notifications.
It may be useful to put this in a loop to constantly be checking for any notifications that may have arrived to your atServer. You will commonly get error code -26624
because most times, there will be nothing to read!
atclient_monitor_response response;
atclient_monitor_response_init(&response);
int exit_code = atclient_monitor_read(&monitor_client, &atclient1, &response, NULL);
// do things with the `response` variable
if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_NOTIFICATION) {
// we should check if this value is populated first, so that we know it is safe for reading.
if (atclient_atnotification_is_decrypted_value_initialized(&response.notification)) {
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Received notification: %s\n", response.notification.decrypted_value);
}
}
atclient_monitor_response_free(&response);
See the Example Application on how to read the different kinds of values from the atclient_monitor_response
struct.
6. Free everything
Don't forget to free everything at the end of your application's life time
atclient_free(&monitor_client);
Also free the atclient_monitor_response
struct if you haven't already.
atclient_monitor_response_free(&response);
Example Application
You can find the full code of this example on our GitHub.
#include <atclient/atclient.h>
#include <atclient/atclient_utils.h>
#include <atclient/constants.h>
#include <atclient/monitor.h>
#include <atlogger/atlogger.h>
#include <stdlib.h>
#define TAG "4a-monitor"
#define ATSIGN "@soccer99"
int main()
{
int exit_code = -1;
atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
char *atserver_host = NULL;
int atserver_port = 0;
atclient_atkeys atkeys;
atclient_atkeys_init(&atkeys);
atclient atclient1;
atclient_init(&atclient1);
atclient monitor_client;
atclient_init(&monitor_client);
if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
{
goto exit;
}
if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
{
goto exit;
}
if ((exit_code = atclient_pkam_authenticate(&atclient1, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
{
goto exit;
}
/*
* Our monitor connection is just an ordinary atclient context, but we will use a completely separate atclient context to manage it.
* And instead of calling the atclient functions on our atclient monitor context, we will use monitor functions on it instead.
* In this case, we are using `atclient_monitor_pkam_authenticate` instead of `atclient_pkam_authenticate` even though it's essentially the same function
* signature (aside from the function name)
*/
if ((exit_code = atclient_monitor_pkam_authenticate(&monitor_client, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
{
goto exit;
}
if((exit_code = atclient_monitor_start(&monitor_client, ".*")) != 0) {
goto exit;
}
while (true)
{
atclient_monitor_response response;
atclient_monitor_response_init(&response);
exit_code = atclient_monitor_read(&monitor_client, &atclient1, &response, NULL);
if (exit_code != 0)
{
if (response.type == ATCLIENT_MONITOR_ERROR_READ)
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Error reading from monitor: %d\n", response.error_read.error_code);
}
else if (response.type == ATCLIENT_MONITOR_ERROR_PARSE_NOTIFICATION)
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Parse notification from monitor: %d\n", exit_code);
}
else if (response.type == ATCLIENT_MONITOR_ERROR_DECRYPT_NOTIFICATION)
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Error response from monitor: %d\n", exit_code);
} else {
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Unknown error: %d\n", exit_code);
}
}
else
{
if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_NOTIFICATION)
{
// we should check if this value is populated first, so that we know it is safe for reading.
if (atclient_atnotification_is_decrypted_value_initialized(&response.notification))
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Received notification: %s\n", response.notification.decrypted_value);
}
}
else if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_DATA_RESPONSE)
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Received data: %s\n", response.data_response); // most times, this is a `data:ok` from a heart beat
}
else if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_ERROR_RESPONSE)
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Received error: %s\n", response.error_response);
}
else if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_NONE)
{
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "No message received\n");
} else {
atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Unknown message type: %d\n", response.type);
}
}
atclient_monitor_response_free(&response);
}
exit_code = 0;
exit:
{
free(atserver_host);
atclient_atkeys_free(&atkeys);
atclient_free(&atclient1);
atclient_free(&monitor_client);
return exit_code;
}
}
Notify
1. Include `notify.h`
#include <atclient/notify.h>
2. Create a Shared atKey
Since we are notifying another atSign, we have to set up a Shared atKey.
This process should be familiar to you if you have already gone through CRUD Operations.
atclient_atkey atkey;
atclient_atkey_init(&atkey);
if(atclient_atkey_create_shared_key(&atkey, "phone", "@jeremy_0", "@soccer99", "c_demos") != 0) {
// an error occurred
}
3. Set up Notify Params
Next, we will set up the parameters before sending the notification. This will essentially hold the instructions on how to send the notification.
The atKey
and value
are the two fields that are mandatory in order to send a notification. If you forget to set these two fields, then you will not be allowed to send a notification because the instructions of sending a notification are incomplete.
atclient_notify_params notify_params;
atclient_notify_params_init(¬ify_params);
if(atclient_notify_params_set_atkey(¬ify_params, &atkey) != 0) {
// an error occurred
}
const char *value = "Hello! This is a notification!";
if(atclient_notify_params_set_value(¬ify_params, value) != 0) {
// an error occurred
}
4. Call `atclient_notify`
Simply call atclient_notify
.This will send a notification to the shared_with
atSign that you specified when creating the Shared atKey which was done in this step.
We will pass NULL
into the commit_id
parameter because we don't reallty care about the commt id for now. You can receive it by passing a int *
if you would like.
if(atclient_notify(&atclient1, ¬ify_params, NULL) != 0) {
// an error occurred
}
5. Free everything
Don't forget to free everything
atclient_atkey_free(&atkey);
atclient_notify_params_free(¬ify_params);
Example Application
You can find the full code of this example on our GitHub.
#include <atclient/atclient.h>
#include <atclient/atclient_utils.h>
#include <atclient/constants.h>
#include <atclient/notify.h>
#include <atlogger/atlogger.h>
#include <stdlib.h>
#define ATSIGN "@soccer0"
int main()
{
int exit_code = -1;
atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
char *atserver_host = NULL;
int atserver_port = 0;
atclient_atkeys atkeys;
atclient_atkeys_init(&atkeys);
atclient atclient1;
atclient_init(&atclient1);
atclient_atkey atkey;
atclient_atkey_init(&atkey);
atclient_notify_params notify_params;
atclient_notify_params_init(¬ify_params);
if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
{
goto exit;
}
if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
{
goto exit;
}
if ((exit_code = atclient_pkam_authenticate(&atclient1, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
{
goto exit;
}
/*
* 1a. Set up atkey in our notify_params struct
*/
if((exit_code = atclient_atkey_create_shared_key(&atkey, "phone", ATSIGN, "@soccer99", "c_demos")) != 0) {
goto exit;
}
if((exit_code = atclient_notify_params_set_atkey(¬ify_params, &atkey)) != 0) {
goto exit;
}
/*
* 1b. Set up value in our notify_params struct
*/
const char *value = "Hello! This is a notification!";
if((exit_code = atclient_notify_params_set_value(¬ify_params, value)) != 0) {
goto exit;
}
/*
* 2. Send the notification
* We will pass `NULL` for the commit id.
*/
if((exit_code = atclient_notify(&atclient1, ¬ify_params, NULL)) != 0) {
goto exit;
}
exit_code = 0;
exit:
{
free(atserver_host);
atclient_atkeys_free(&atkeys);
atclient_free(&atclient1);
atclient_atkey_free(&atkey);
atclient_notify_params_free(¬ify_params);
return exit_code;
}
}
Last updated