diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 2590d67..ab659fd 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -5,11 +5,14 @@ * [atSign](learn/core/atsign.md) * [atRecord](learn/core/atrecord.md) * [atSDK](learn/sdk/README.md) - * [Onboarding](learn/sdk/onboarding.md) + * [Get Started](sdk/get-started.md) + * [Authentication](learn/sdk/onboarding.md) * [atKey Reference](learn/sdk/atid-reference.md) * [CRUD Operations](learn/sdk/crud-operations.md) - * [Events](learn/sdk/events.md) - * [Synchronization](learn/sdk/synchronization.md) + * [Notifications](learn/sdk/events.md) + * [Additional Features](sdk/synchronization/README.md) + * [Synchronization](sdk/synchronization/synchronization.md) + * [Connection Hooks](sdk/synchronization/connection-hooks.md) * [Infrastructure](infrastructure.md) ## 🗒️ Tutorials @@ -22,7 +25,6 @@ * [Send and Receive data synchronously.](tutorials/atsdk-tutorial/send-and-receive-data-synchronously.md) * [Remote Procedure Calls (RPC)](tutorials/atsdk-tutorial/rpc.md) * [atTalk - Encrypted chat client](tutorials/atsdk-tutorial/attalk.md) -* [Starter Flutter App](tutorials/flutter-and-atsdk-app.md) ## Related pages diff --git a/docs/learn/sdk/README.md b/docs/learn/sdk/README.md index 9d84e9b..2b497f8 100644 --- a/docs/learn/sdk/README.md +++ b/docs/learn/sdk/README.md @@ -2,10 +2,8 @@ ## Overview -An atClient is any client application which uses the atProtocol to interact with an atServer. This can be anything from a desktop application to a small background service running on a microcontroller. - -The atClient SDKs provide everything you need to write an application which implements the atProtocol. This SDK reference is generic and has been broken up into sections to show the basic principles, to get coding take a look at the [tutorials](broken-reference). +The atSDK is the best way to embed the atProtocol into new or existing software. This can be anything from a graphical desktop application to firmware flashed on a microcontroller. ### Sections -
LinkDescription
onboarding.mdHow to authenticate an atClient to an atServer
atid-reference.mdLearn how to create atKeys for your chosen platform
crud-operations.mdHow to do basic CRUD operations on an atServer
events.mdHow to send and receive real-time events
synchronization.mdHow to sync and persist data with an atServer
+
LinkDescription
get-started.mdSetup the atSDK for your preferred language
onboarding.mdHow to authenticate to an atServer
atid-reference.mdLearn how to create atKeys for your chosen platform
crud-operations.mdHow to do basic CRUD operations on an atServer
events.mdHow to send and receive real-time messages
synchronizationImplementation specific features to know about
diff --git a/docs/learn/sdk/atid-reference.md b/docs/learn/sdk/atid-reference.md index a8f527e..6ac4b61 100644 --- a/docs/learn/sdk/atid-reference.md +++ b/docs/learn/sdk/atid-reference.md @@ -1,3 +1,7 @@ +--- +description: Learn how to create atKeys for your chosen platform +--- + # atKey Reference {% hint style="warning" %} @@ -7,16 +11,18 @@ Please note that any reference to the word "AtKey" in this document is not assoc {% endhint %} {% hint style="warning" %} -This article explains how to create an atKey in the atClient SDK. +This article explains how to create an atKey in the atSDK. If you are unfamiliar with atKeys please read [this](../core/atrecord.md#atid) first. {% endhint %} {% tabs %} {% tab title="Flutter / Dart" %} -The [at\_commons](https://pub.dev/packages/at\_commons) package contains common elements used in a number of Atsign's Flutter and Dart packages. This package needs to be included in the application in order to create atKeys. +## Flutter / Dart + +### Package Installation -### Installation +The [at\_commons](https://pub.dev/packages/at\_commons) package contains common elements used in a number of Atsign's Flutter and Dart packages. This package needs to be included in the application in order to create atKeys. First add the package to your project: @@ -24,19 +30,6 @@ First add the package to your project: flutter pub add at_commons ``` -Or add the package to your `pubspec.yaml` manually: - -{% code title="pubspec.yaml" %} -```yaml -dependencies: - flutter: - sdk: flutter - at_client_mobile: ^3.2.9 - at_onboarding_flutter: ^6.0.3 - at_commons: ^3.0.45 -``` -{% endcode %} - ### Usage See below for how to create the various types of atKeys. @@ -87,32 +80,6 @@ AtKey mySelfID = AtKey.self('phone', namespace: 'wavi', sharedBy: '@alice').buil ``` {% endcode %} -#### Private atKey - -To create a private atKey, first use the `AtKey.private` builder to configure it, then call `.build` to create it. - -{% hint style="info" %} -The difference between a private and a self atkey is that the private atKey is hidden by default when using the [atProtocol scan verb](https://github.com/atsign-foundation/at\_protocol/blob/trunk/specification/at\_protocol\_specification.md#the-scan-verb). -{% endhint %} - -{% code title="AtKey.private signature" %} -```dart -static PrivateKeyBuilder private(String key, {String? namespace}) -``` -{% endcode %} - -The `build` method on `PrivateKeyBuilder` takes no parameters. - -**Example** - -`privatekey:phone.wavi@alice` - -{% code overflow="wrap" %} -```dart -AtKey myPrivateID = AtKey.private('phone', namespace: 'wavi').build(); -``` -{% endcode %} - #### Shared atKey To create a shared atKey, first use the `AtKey.shared` builder to configure it, then call `.build` to create it. @@ -156,4 +123,368 @@ You can find the API reference for the entire package available on [pub](https:/ The `AtKey` class API reference is available [here](https://pub.dev/documentation/at\_commons/latest/at\_commons/AtKey-class.html). {% endtab %} + +{% tab title="C" %} +## C + +### Introduction + +There are three kinds of atKeys: + +1. [Public atKey](atid-reference.md#public-atkey) +2. [Self atKey](atid-reference.md#self-atkey) +3. [Shared atKey](atid-reference.md#shared-atkey) + +[Public atKey](atid-reference.md#public-atkey) holds public (and non-encrypted) data, available for any atSign to get from you. + +[Self atKey](atid-reference.md#self-atkey) holds self encrypted data, only available for your own atSign to get. + +[Shared atKey](atid-reference.md#shared-atkey) holds encrypted data that is only decipherable by you and the intended recipient. + +Every atKey has [metadata](atid-reference.md#metadata), which is free for you to also control (to an extent). Not all metadata should be handled by the developer. Some metadata is managed by the SDK itself. Check out our [documentation](../core/atrecord.md) on metadata to find out which metadata is worth your time handling. + +Before running any of the examples, be sure to include `atkey.h` + +```c +#include +``` + +### Public atKey + +This is how you create a Public atKey. + +First, create the atkey struct. + +```c +atclient_atkey my_public_atkey; +atclient_atkey_init(&my_public_atkey); +``` + +Next, call the `atclient_atkey_create_public_key` function. + +```c +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_namespace = "wavi"; + +if(atclient_atkey_create_public_key( + &my_public_atkey, + atkey_key, + atkey_shared_by, + atkey_namespace) != 0) { + // an error occurred +} +``` + +Don't forget to call `atclient_atkey_free` at the end of the intended life-time of your atkey struct. + +```c +atclient_atkey_free(&my_public_atkey); +``` + +#### Example Application + +```c +#include +#include +#include +#include + +#define ATSIGN "@soccer99" + +int main() +{ + int exit_code = -1; + + /* + * Create an atkey struct + */ + atclient_atkey my_public_atkey; + atclient_atkey_init(&my_public_atkey); + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_namespace = "wavi"; + + /* + * Use the dandy atkey_create_public_key function to populate the struct for you with your desired values. + */ + if((exit_code = atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) { + goto exit; + } + + exit_code = 0; +exit: +{ + atclient_atkey_free(&my_public_atkey); + return exit_code; +} +} +``` + +### Self atKey + +This is how you create a Self atKey. + +First, create the `atclient_atkey struct` + +
atclient_atkey my_self_atkey;
+atclient_atkey_init(&my_self_atkey);
+
+ +Next, call the `atclient_atkey_create_self_key` function. + +```c +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_namespace = "wavi"; + +if(atclient_atkey_create_self_key( + &my_self_atkey, + atkey_key, + atkey_shared_by, + atkey_namespace) != 0) { + // an error occurred +} +``` + +Don't forget to call `atclient_atkey_free` at the end of the intended life-time of your atkey struct. + +```c +atclient_atkey_free(&my_public_atkey); +``` + +#### Example Application + +```c +#include +#include +#include +#include + +#define ATSIGN "@soccer99" + +int main() +{ + int exit_code = -1; + + /* + * Create an atkey struct + */ + atclient_atkey my_self_atkey; + atclient_atkey_init(&my_self_atkey); + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_namespace = "wavi"; + + /* + * Use the dandy atkey_create_self_key function to populate the struct for you with your desired values. + */ + if((exit_code = atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) { + goto exit; + } + + exit_code = 0; +exit: +{ + atclient_atkey_free(&my_self_atkey); + return exit_code; +} +} +``` + +### Shared atKey + +This is how you create a Shared atKey. + +First, create the `atclient_atkey` struct. + +```c +atclient_atkey my_shared_atkey; +atclient_atkey_init(&my_shared_atkey); +``` + +Next, call the `atclient_atkey_create_shared_key` function. + +```c +const char *atkey_key = "phone"; +const char *atkey_shared_by = ATSIGN; +const char *atkey_shared_with = "@soccer99"; +const char *atkey_namespace = "wavi"; + +if(atclient_atkey_create_shared_key(&my_shared_atkey, + atkey_key, + atkey_shared_by, + atkey_shared_with, + atkey_namespace) != 0) { + // an error occurred +} +``` + +Don't forget to call `atclient_atkey_free` at the end of the intended life-time of your atkey struct. + +```c +atclient_atkey_free(&my_public_atkey); +``` + +#### Example Application + +```c +#include +#include +#include +#include + +#define ATSIGN "@soccer99" + +int main() +{ + int exit_code = -1; + + /* + * Create an atkey struct + */ + atclient_atkey my_shared_atkey; + atclient_atkey_init(&my_shared_atkey); + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_shared_with = "@soccer99"; + const char *atkey_namespace = "wavi"; + + /* + * Use the dandy atkey_create_shared_key function to populate the struct for you with your desired values. + */ + if((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0) { + goto exit; + } + + + exit_code = 0; +exit: +{ + atclient_atkey_free(&my_shared_atkey); + return exit_code; +} +} +``` + +### Metadata + +This is how you modify the metadata of any atKey. For the sake of this example, we will modify the metadata of a Shared atKey. + +First, create your atKey as usual. + +```c +atclient_atkey my_shared_atkey; +atclient_atkey_init(&my_shared_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_shared_with = "@soccer99"; +const char *atkey_namespace = "wavi"; + +if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +Next, let's create a pointer to the atKey's metadata. We can feel safe reading the `atclient_atkey`'s inner metadata field because we have previously called `atclient_atkey_init`. + +```c +atclient_atkey_metadata *metadata = &(my_shared_atkey.metadata); +``` + +Call a `atclient_atkey_metadata_set_*` function. For the sake of this example, we will call `atclient_atkey_metadata_set_ttl` which sets the atKey's lifespan, specified in milliseconds. + +```c +if (atclient_atkey_metadata_set_ttl(metadata, 1 * 1000) != 0) +{ + // an error occurred +} +``` + +We can check if that was set and is safe to read by using the `atclient_atkey_metadata_is_ttl_initialized(metadata)` function. It is important to check that `ttl` was initialized. If this function returns true, we can feel safe knowing that the the internal initialized bit was set to true previously and that we are not reading garbage data. + +```c +if(atclient_atkey_metadata_is_ttl_initialized(metadata)) { + // metadata->ttl is safe to read and we know it is populated. + printf("metadata->ttl: %d\n", metadata->ttl); +} +``` + +#### Example Application + +```c +#include +#include +#include +#include + +#define ATSIGN "@soccer99" + +#define ATSERVER_HOST "root.atsign.org" +#define ATSERVER_PORT 64 + +int main() +{ + int exit_code = -1; + + atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG); + + atclient_atkey my_shared_atkey; + atclient_atkey_init(&my_shared_atkey); + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_shared_with = "@soccer99"; + const char *atkey_namespace = "wavi"; + + if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0) + { + goto exit; + } + + /* + * Now that the AtKey is properly set up, we can set the metadata of that AtKey like this. + * Since we called the `atclient_atkey_init` function earlier, we can feel safe knowing that the metadata is also already initialized. + */ + atclient_atkey_metadata *metadata = &(my_shared_atkey.metadata); + + /* + * Set the ttl (time to live) of the AtKey. Once this AtKey is put into the atServer, it will only live for 1000 milliseconds. + */ + if ((exit_code = atclient_atkey_metadata_set_ttl(metadata, 1 * 1000)) != 0) + { + goto exit; + } + + /* + * Set the ttr (time to refresh) of the AtKey. Once this AtKey is put into the atServer, the recipient of the AtKey will have it refreshed every 1000 + * milliseconds, in case there are any value changes. + */ + if ((exit_code = atclient_atkey_metadata_set_ttr(metadata, 1 * 1000)) != 0) + { + goto exit; + } + + if(atclient_atkey_metadata_is_ttl_initialized(metadata)) { + // metadata->ttl is safe to read and we know it is populated. + atlogger_log("main", ATLOGGER_LOGGING_LEVEL_DEBUG, "metadata->ttl: %d\n", metadata->ttl); // [DEBG] 2024-08-15 01:52:09.596055 | main | metadata->ttl: 1000 + } + + exit_code = 0; +exit: +{ + atclient_atkey_free(&my_shared_atkey); // this _free command will automatically free the metadata as well + return exit_code; +} +} +``` + +x +{% endtab %} {% endtabs %} + diff --git a/docs/learn/sdk/crud-operations.md b/docs/learn/sdk/crud-operations.md index d8044c9..87bfb7b 100644 --- a/docs/learn/sdk/crud-operations.md +++ b/docs/learn/sdk/crud-operations.md @@ -1,5 +1,5 @@ --- -description: Basic create, read, update and delete operations with atRecords +description: How to do basic CRUD operations on an atServer --- # CRUD Operations @@ -173,16 +173,908 @@ Future delete( bool res = await atClient.delete(myID); ``` +### Additional Features + +See [#additional-features](crud-operations.md#additional-features "mention") to learn about synchronization, which supports syncing of a local atServer, allowing CRUD operations to work even if the application has no internet access. + ### API Docs You can find the API reference for the entire package available on [pub](https://pub.dev/documentation/at\_client/latest/). The `AtClient` class API reference is available [here](https://pub.dev/documentation/at\_client/latest/at\_client/AtClient-class.html). {% endtab %} + +{% tab title="C" %} +## C + +### Table of Contents + +* [#introduction](crud-operations.md#introduction "mention") +* [#put-public-atkey](crud-operations.md#put-public-atkey "mention") + * [#id-1.-create-public-atkey](crud-operations.md#id-1.-create-public-atkey "mention") + * [#id-2.-call-atclient\_put\_public\_key](crud-operations.md#id-2.-call-atclient\_put\_public\_key "mention") + * [#example-application](crud-operations.md#example-application "mention") +* [#put-self-atkey](crud-operations.md#put-self-atkey "mention") + * [#id-1.-create-a-self-atkey](crud-operations.md#id-1.-create-a-self-atkey "mention") + * [#id-2.-call-atclient\_put\_self\_key](crud-operations.md#id-2.-call-atclient\_put\_self\_key "mention") + * [#example-application-1](crud-operations.md#example-application-1 "mention") +* [#put-shared-atkey](crud-operations.md#put-shared-atkey "mention") + * [#id-1.-create-a-shared-atkey](crud-operations.md#id-1.-create-a-shared-atkey "mention") + * [#id-2.-call-atclient\_put\_shared\_key](crud-operations.md#id-2.-call-atclient\_put\_shared\_key "mention") + * [#example-application-2](crud-operations.md#example-application-2 "mention") +* [#get-public-atkey](crud-operations.md#get-public-atkey "mention") + * [#id-1.-create-public-atkey-1](crud-operations.md#id-1.-create-public-atkey-1 "mention") + * [#id-2.-call-atclient\_get\_public\_key](crud-operations.md#id-2.-call-atclient\_get\_public\_key "mention") + * [#id-3.-free-value](crud-operations.md#id-3.-free-value "mention") + * [#example-application-3](crud-operations.md#example-application-3 "mention") +* [#get-self-atkey](crud-operations.md#get-self-atkey "mention") + * [#id-1.-create-a-self-atkey-1](crud-operations.md#id-1.-create-a-self-atkey-1 "mention") + * [#id-2.-call-atclient\_get\_self\_key](crud-operations.md#id-2.-call-atclient\_get\_self\_key "mention") + * [#id-3.-free-value-1](crud-operations.md#id-3.-free-value-1 "mention") + * [#example-application-4](crud-operations.md#example-application-4 "mention") +* [#get-shared-atkey](crud-operations.md#get-shared-atkey "mention") + * [#id-1.-create-a-shared-atkey-1](crud-operations.md#id-1.-create-a-shared-atkey-1 "mention") + * [#id-2.-call-atclient\_get\_shared\_key](crud-operations.md#id-2.-call-atclient\_get\_shared\_key "mention") + * [#id-3.-free-value-2](crud-operations.md#id-3.-free-value-2 "mention") + * [#example-application-5](crud-operations.md#example-application-5 "mention") +* [#delete-an-atkey](crud-operations.md#delete-an-atkey "mention") + * [#id-1.-create-an-atkey](crud-operations.md#id-1.-create-an-atkey "mention") + * [#id-2.-call-atclient\_delete](crud-operations.md#id-2.-call-atclient\_delete "mention") + * [#example-application-6](crud-operations.md#example-application-6 "mention") +* [#request-options](crud-operations.md#request-options "mention") + +### Introduction + +In this section, we will learn how to `put`, `get`, and `delete` an atKey. + +If you are unfamiliar with the different atKey types, check out our documentation on [atRecords](../core/atrecord.md). + +### Put Public atKey + +#### 1. Create Public AtKey + +First, create a public atKey. It is important to note that the `shared_by` atSign should be the same as the authenticated atSign in the application. + +```c +atclient_atkey my_public_atkey; +atclient_atkey_init(&my_public_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_namespace = "c_demos"; + +if (atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_put\_public\_key\` + +Next, simply \*put\* the value into your atServer. Since we are putting a public value into our atServer, no data will be encrypted and this data will be available for any atSign to get. + +We will pass `NULL` into the request\_options and commit\_id parameters because we want to use the default options for now and we don't particularly care about the commit\_id that it returns, but you could receive it if you would like. + +This function returns an `int` for error handling, in which a non-zero exit code indicates an error. + +```c +const char *atkey_value = "123-456-7890"; + +if (atclient_put_public_key(&atclient, &my_public_atkey, atkey_value, NULL, NULL) != 0) +{ + // an error occurred +} +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#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 atclient; + atclient_init(&atclient); + + atclient_atkey my_public_atkey; + atclient_atkey_init(&my_public_atkey); + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) + { + goto exit; + } + + const char *atkey_value = "123-456-7890"; + + /* + * atclient_put_self_key lets you put a key-value pair in your atSign's atServer. + * For our purposes, we will pass `NULL` for the request options and the commit id. + * We want to use the default options and we don't want to receive and + * store the commit id. + */ + if ((exit_code = atclient_put_public_key(&atclient, &my_public_atkey, atkey_value, NULL, NULL)) != 0) + { + goto exit; + } + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_public_atkey); + return exit_code; +} +} +``` + +### Put Self atKey + +#### 1. Create a Self atKey + +```c +atclient_atkey my_self_atkey; +atclient_atkey_init(&my_self_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_namespace = "c_demos"; + +if (atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_put\_self\_key\` + +This will put a value specially encrypted for your atServer that only the atSign's atKeys can decrypt. + +We will pass `NULL` into the request\_options and commit\_id parameters because we want to use the default options for now and we don't particularly care about the commit\_id that it returns, but you could receive it if you would like. + +This function will return an `int` for error handling, in which a non-zero exit code indicates an error. + +```c +const char *atkey_value = "123-456-7890"; + +if (atclient_put_self_key(&atclient, &my_self_atkey, atkey_value, NULL, NULL) != 0) +{ + // an error occurred +} +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#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 atclient; + atclient_init(&atclient); + + atclient_atkey my_self_atkey; + atclient_atkey_init(&my_self_atkey); + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) + { + goto exit; + } + + const char *atkey_value = "123-456-7890"; + + /* + * atclient_put_self_key lets you put a key-value pair in your atSign's atServer. + * For our purposes, we will pass `NULL` for the request options and the commit id. + * We want to use the default options and we don't want to receive and + * store the commit id. + */ + if ((exit_code = atclient_put_self_key(&atclient, &my_self_atkey, atkey_value, NULL, NULL)) != 0) + { + goto exit; + } + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_self_atkey); + return exit_code; +} +} + +``` + +### Put Shared atKey + +#### 1. Create a Shared atKey + +```c +atclient_atkey my_shared_atkey; +atclient_atkey_init(&my_shared_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_shared_with = "@soccer0"; +const char *atkey_namespace = "c_demos"; +if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_put\_shared\_key\` + +This function will put our string value into the atServer. Since we are using a Shared atKey, that means only the `shared_by` and `shared_with` atSign will be able to decrypt this value. + +We will pass `NULL` into the request\_options and commit\_id parameters because we want to use the default options for now and we don't particularly care about the commit\_id that it returns, but you could receive it if you would like. + +This function returns an `int` for error handling, in which a non-zero exit code indicates an error. + +```c +const char *atkey_value = "123-456-7890"; +if (atclient_put_shared_key(&atclient, &my_shared_atkey, atkey_value, NULL, NULL) != 0) +{ + // an error occurred +} +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#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 atclient; + atclient_init(&atclient); + + atclient_atkey my_shared_atkey; + atclient_atkey_init(&my_shared_atkey); + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_shared_with = "@soccer0"; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0) + { + goto exit; + } + + const char *atkey_value = "123-456-7890"; + + /* + * atclient_put_self_key lets you put a key-value pair in your atSign's atServer. + * For our purposes, we will pass `NULL` for the request options and the commit id. + * We want to use the default options and we don't want to receive and + * store the commit id. + */ + if ((exit_code = atclient_put_shared_key(&atclient, &my_shared_atkey, atkey_value, NULL, NULL)) != 0) + { + goto exit; + } + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_shared_atkey); + return exit_code; +} +} +``` + +### Get Public atKey + +#### 1. Create Public AtKey + +First, create a public atKey. It is important to note that the `shared_by` atSign should be the same as the authenticated atSign in the application. + +```c +atclient_atkey my_public_atkey; +atclient_atkey_init(&my_public_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_namespace = "c_demos"; + +if (atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_get\_public\_key\` + +We will create a variable named `value` and pass the address to it in our `atclient_get_public_key` function call. The function will allocate memory for us and populate that variable for us. + +We will pass `NULL` into the request\_options parameter because we want to use the default options for now. + +This function returns an `int` for error handling, in which a non-zero exit code indicates an error. + +```c +char *value = NULL; +if (atclient_get_public_key(&atclient, &my_public_atkey, &value, NULL) != 0) +{ + // an error occurred +} +``` + +#### 3. Free \`value\` + +Once we are done with the value itself, we should free it to avoid any memory leaks. + +```c +free(value); +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#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 atclient; + atclient_init(&atclient); + + atclient_atkey my_public_atkey; + atclient_atkey_init(&my_public_atkey); + + /* + * Let's declare a null pointer which will later be allocated by our `atclient_get_public_key` function. It is our responsibility to free it once we are + * done with it. + */ + char *value = NULL; + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) + { + goto exit; + } + + /* + * `atclient_get_public_key` will allocate memory for the `value` pointer. It is our responsibility to free it once we are done with it. + * We will pass `NULL` into the request_options argument for now and just use the default request options. + */ + if ((exit_code = atclient_get_public_key(&atclient, &my_public_atkey, &value, NULL)) != 0) + { + goto exit; + } + + atlogger_log("3d-get-public-atkey", ATLOGGER_LOGGING_LEVEL_INFO, "value: \"%s\"\n", value); + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_public_atkey); + free(value); + return exit_code; +} +} +``` + +### Get Self atKey + +#### 1. Create a Self atKey + +```c +atclient_atkey my_self_atkey; +atclient_atkey_init(&my_self_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_namespace = "c_demos"; + +if (atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_get\_self\_key\` + +We will create a variable named `value` and pass the address to it in our `atclient_get_self_key` function call. The function will allocate memory for us and populate that variable for us. + +We will pass `NULL` into the request\_options parameter because we want to use the default options for now. + +This function returns an `int` for error handling, in which a non-zero exit code indicates an error. + +```c +char *value = NULL; +if (atclient_get_self_key(&atclient, &my_self_atkey, &value, NULL) != 0) +{ + // an error occurred +} +``` + +#### 3. Free \`value\` + +Once we are done with the value itself, we should free it to avoid any memory leaks. + +```c +free(value); +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#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 atclient; + atclient_init(&atclient); + + atclient_atkey my_self_atkey; + atclient_atkey_init(&my_self_atkey); + + /* + * Let's declare a null pointer which will later be allocated by our `atclient_get_public_key` function. It is our responsibility to free it once we are + * done with it. + */ + char *value = NULL; + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) + { + goto exit; + } + + /* + * `atclient_get_self_key` will allocate memory for the `value` pointer. It is our responsibility to free it once we are done with it. + * We will pass `NULL` into the request_options argument for now and just use the default request options. + */ + if ((exit_code = atclient_get_self_key(&atclient, &my_self_atkey, &value, NULL)) != 0) + { + goto exit; + } + + atlogger_log("3E-get-self-atkey", ATLOGGER_LOGGING_LEVEL_INFO, "value: \"%s\"\n", value); + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_self_atkey); + free(value); + return exit_code; +} +} +``` + +### Get Shared atKey + +#### 1. Create a Shared atKey + +```c +atclient_atkey my_shared_atkey; +atclient_atkey_init(&my_shared_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_shared_with = "@soccer0"; +const char *atkey_namespace = "c_demos"; +if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_get\_shared\_key\` + +We will create a variable named `value` and pass the address to it in our `atclient_get_shared_key` function call. The function will allocate memory for us and populate that variable for us. + +We will pass `NULL` into the request\_options parameter because we want to use the default options for now. + +This function returns an `int` for error handling, in which a non-zero exit code indicates an error. + +```c +char *value = NULL; +if (atclient_get_shared_key(&atclient, &my_self_atkey, &value, NULL) != 0) +{ + // an error occurred +} +``` + +#### 3. Free \`value\` + +Once we are done with the value itself, we should free it to avoid any memory leaks. + +```c +free(value) +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#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 atclient; + atclient_init(&atclient); + + atclient_atkey my_shared_atkey; + atclient_atkey_init(&my_shared_atkey); + + /* + * Let's declare a null pointer which will later be allocated by our `atclient_get_public_key` function. It is our responsibility to free it once we are + * done with it. + */ + char *value = NULL; + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_shared_with = "@soccer0"; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0) + { + goto exit; + } + + /* + * `atclient_get_self_key` will allocate memory for the `value` pointer. It is our responsibility to free it once we are done with it. + * We will pass `NULL` into the request_options argument for now and just use the default request options. + */ + if ((exit_code = atclient_get_shared_key(&atclient, &my_shared_atkey, &value, NULL)) != 0) + { + goto exit; + } + + atlogger_log("3E-get-self-atkey", ATLOGGER_LOGGING_LEVEL_INFO, "value: \"%s\"\n", value); + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_shared_atkey); + free(value); + return exit_code; +} +} +``` + +### Delete an atKey + +#### 1. Create an AtKey + +First step is to create the atKey that you wish to delete. This can be of any atKey type (public, self, or shared). What is important to note is that you can only delete an atKey that you own (which means that the authenticated atSign is the same as the shared\_by atSign). This should be obvious because you can only delete atKeys that have once been created by you. Only the rightful owners of the atKey that was created can delete it. + +For the sake of this demo, we will create a Shared atKey. + +```c +atclient_atkey my_shared_atkey; +atclient_atkey_init(&my_shared_atkey); + +const char *atkey_key = "phone"; +const char *atkey_shared_by = "@jeremy_0"; +const char *atkey_shared_with = "@soccer0"; +const char *atkey_namespace = "c_demos"; +if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0) +{ + // an error occurred +} +``` + +#### 2. Call \`atclient\_delete\` + +`atclient_delete` will delete the atKey from your atServer. + +We will pass `NULL` to the `request_options` and `commit_id` parameter because we want to use the default options for now and we do not care about the commit\_id. You can receive the commit\_id if you would like by passing an int pointer. + +It is pointless to set any metadata to the atKey when deleting. Metadata is only useful when getting (you can read any metadata that the key possesses) and putting (you can modify the atKey's behavior). When you are deleting, no metadata is used. + +This functions returns an `int` for error handling. A non-zero exit code indicates an error. + +```c +if (atclient_delete(&atclient, &my_shared_atkey, NULL, NULL) != 0) { + // an error occurred +} +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include + +#define ATSIGN "@jeremy_0" + +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 atclient; + atclient_init(&atclient); + + atclient_atkey my_shared_atkey; + atclient_atkey_init(&my_shared_atkey); + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + const char *atkey_key = "phone"; + const char *atkey_shared_by = ATSIGN; + const char *atkey_shared_with = "@soccer0"; + const char *atkey_namespace = "c_demos"; + + if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0) + { + goto exit; + } + + /* + * `atclient_delete` will delete this shared atkey from our atServer. It is important to note that only the `shared_by` atSign can delete the shared atKey + * When deleting, the `shared_by` atSign should always be the authenticated atSign in the `atclient` object. + * We will pass `NULL` to the request_options and commit_id parameters because we want to use the default request_options and we don't care about the + * commit_id we get back. + */ + if ((exit_code = atclient_delete(&atclient, &my_shared_atkey, NULL, NULL) != 0)) { + goto exit; + } + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + atclient_atkey_free(&my_shared_atkey); + return exit_code; +} +} +``` + +### Request Options + +{% hint style="info" %} +Under Construction +{% endhint %} +{% endtab %} {% endtabs %} + + [^1]: This has been deprecated, and will be ignored. [^2]: If metaData.isBinary is true, then this will be a List\. diff --git a/docs/learn/sdk/events.md b/docs/learn/sdk/events.md index d4a67e4..aaa9828 100644 --- a/docs/learn/sdk/events.md +++ b/docs/learn/sdk/events.md @@ -1,8 +1,8 @@ --- -description: Pub/sub events with atRecords +description: How to send and receive real-time messages --- -# Events +# Notifications {% tabs %} {% tab title="Flutter / Dart" %} @@ -30,16 +30,429 @@ As previously mentioned, the AtClientManager stores the actual AtClient itself. AtClient atClient = atClientManager.atClient; ``` -#### atKey +### Monitor -Before you can do anything with an atRecord, you need an atKey to represent it. +You can subscribe to a stream of notifications like this: -If you don't know how to create an atKey, please see the [reference](atid-reference.md) first. +```dart +AtClient atClient = AtClientManager.getInstance().atClient; +atClient.notificationService + .subscribe(regex: 'message.$nameSpace@', shouldDecrypt: true) + .listen((notification) { + print("Got a message: ${notification.value}"); + }); +``` -{% hint style="info" %} -The following examples use the self atKey`phone.wavi@` +### Notify -It is up to the developer to modify the atKey according to their use case. -{% endhint %} +You can send (a.k.a. publish) a notification like this: + +```dart +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), +); +``` +{% endtab %} + +{% tab title="C" %} +## C + +### Table of Contents + +* [#introduction](events.md#introduction "mention") +* [#monitor](events.md#monitor "mention") + * [#id-1.-include-monitor.h](events.md#id-1.-include-monitor.h "mention") + * [#id-2.-create-a-monitor-context](events.md#id-2.-create-a-monitor-context "mention") + * [#id-3.-call-atclient\_monitor\_pkam\_authenticate](events.md#id-3.-call-atclient\_monitor\_pkam\_authenticate "mention") + * [#id-4.-call-atclient\_monitor\_start](events.md#id-4.-call-atclient\_monitor\_start "mention") + * [#id-5.-call-atclient\_monitor\_read](events.md#id-5.-call-atclient\_monitor\_read "mention") + * [#id-6.-free-everything](events.md#id-6.-free-everything "mention") + * [#example-application](events.md#example-application "mention") +* [#notify](events.md#notify "mention") + * [#id-1.-include-notify.h](events.md#id-1.-include-notify.h "mention") + * [#id-2.-create-a-shared-atkey](events.md#id-2.-create-a-shared-atkey "mention") + * [#id-3.-set-up-notify-params](events.md#id-3.-set-up-notify-params "mention") + * [#id-4.-call-atclient\_notify](events.md#id-4.-call-atclient\_notify "mention") + * [#id-5.-free-everything](events.md#id-5.-free-everything "mention") + * [#example-application-1](events.md#example-application-1 "mention") + +### 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 atSign +* `monitor` is how we listen for real-time messages + +### Monitor + +#### 1. Include \`monitor.h\` + +```c +#include +``` + +#### 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. + +```css +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. + +```c +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! + +```c +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-1](events.md#example-application-1 "mention") 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 + +```c +atclient_free(&monitor_client); +``` + +Also free the `atclient_monitor_response` struct if you haven't already. + +```c +atclient_monitor_response_free(&response); +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include +#include + +#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\` + +```c +#include +``` + +#### 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.md](crud-operations.md "mention"). + +```c +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. + +```c +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](events.md#id-2.-create-a-shared-atkey). + +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. + +```c +if(atclient_notify(&atclient1, ¬ify_params, NULL) != 0) { + // an error occurred +} +``` + +#### 5. Free everything + +Don't forget to free everything + +```c +atclient_atkey_free(&atkey); +atclient_notify_params_free(¬ify_params); +``` + +#### Example Application + +```c +#include +#include +#include +#include +#include +#include + +#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; +} +} +``` {% endtab %} {% endtabs %} + diff --git a/docs/learn/sdk/onboarding.md b/docs/learn/sdk/onboarding.md index 70a8d12..bd012f7 100644 --- a/docs/learn/sdk/onboarding.md +++ b/docs/learn/sdk/onboarding.md @@ -1,41 +1,24 @@ --- -description: Authenticate the atClient with the atServer +description: How to authenticate to an atServer --- -# Onboarding - -## Overview - -Onboarding is the process of authenticating to the atServer using it's associated atSign. Instead of the traditional username and password, this process uses the atSign's secret cryptographic keys to perform authentication. - -## Setup Process - -Depending on the SDK you are using, the process for onboarding varies. +# Authentication {% tabs %} {% tab title="Dart" %} -### Dart - -In Dart we provide the[ at\_onboarding\_cli](https://pub.dev/packages/at\_onboarding\_cli) package which handles onboarding to the atServer via files stored in the \~/.atsign/keys directory +## Dart -#### Manual Configuration +### **Package Installation** -**Installation** +In Dart we provide the[ at\_onboarding\_cli](https://pub.dev/packages/at\_onboarding\_cli) package which handles onboarding to the atServer via files stored in the \~/.atsign/keys directory -First add the package to your project +Add the package to your project automatically using pub: -```dart -dart pub add at_onboarding_cli ``` - -Or add the package to your `pubspec.yaml` manually: - -```yaml -dependencies: - at_onboarding_cli: ^1.3.0 +dart pub add at_onboarding_cli ``` -#### Usage +### Usage Set up the [preferences](https://pub.dev/documentation/at\_onboarding\_cli/latest/at\_onboarding\_cli/AtOnboardingPreference-class.html) to onboard to the atServer. @@ -52,7 +35,7 @@ Set up the [preferences](https://pub.dev/documentation/at\_onboarding\_cli/lates ..atProtocolEmitted = Version(2, 0, 0); ``` -Next get the onBoardingService +Next get the onboardingService ```dart AtOnboardingService onboardingService = AtOnboardingServiceImpl( @@ -92,76 +75,254 @@ bool onboarded = false; {% endtab %} {% tab title="Flutter" %} -### Flutter +## Flutter -In Flutter, we provide the [at\_onboarding\_flutter](https://pub.dev/packages/at\_onboarding\_flutter) package which handles secure management of these secret keys. - -{% hint style="info" %} -If you are developing a new Flutter app, we recommend that you use [at\_app](https://pub.dev/packages/at\_app) to generate an app skeleton with the at\_onboarding\_flutter package preconfigured. +{% hint style="warning" %} +If you followed the [get-started.md](../../sdk/get-started.md "mention") guide for Flutter, then you should already have onboarding setup in your app. Feel free to skip this section and move on. {% endhint %} -#### Manual Configuration +### Package Installation -**Installation** +In Flutter, we provide the [at\_onboarding\_flutter](https://pub.dev/packages/at\_onboarding\_flutter) package which handles secure management of these secret keys. -First add the package to your project: +Add the package to your project automatically using pub: ``` flutter pub add at_onboarding_flutter ``` -Or add the package to your `pubspec.yaml` manually: - -{% code title="pubspec.yaml" %} -```yaml -dependencies: - flutter: - sdk: flutter - at_client_mobile: ^3.2.9 - at_onboarding_flutter: ^6.0.3 -``` -{% endcode %} - -**Usage** +### Usage Simply call the [`onboard`](https://pub.dev/documentation/at\_onboarding\_flutter/latest/at\_onboarding/AtOnboarding/onboard.html) method whenever you want your app to open the onboarding widget. ```dart -AtOnboarding.onboard( - context: context, // BuildContext +AtOnboardingResult onboardingResult = await AtOnboarding.onboard( + context: context, config: AtOnboardingConfig( - atClientPreference: AtClientPreference() - ..rootDomain = AtEnv.rootDomain // access AtEnv from the `at_app_flutter` package - ..namespace = AtEnv.appNamespace - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true, - rootEnvironment: AtEnv.rootEnvironment, - domain: AtEnv.rootDomain, - appAPIKey: AtEnv.appApiKey, + atClientPreference: futurePreference, + rootEnvironment: RootEnvironment.Production, + appAPIKey: dotenv.env['API_KEY'], + domain: 'root.atsign.org', ), ); ``` -{% hint style="info" %} -`dir` is a variable from holding data retrieved from the [path\_provider](https://pub.dev/packages/path\_provider) package: `var dir = await getApplicationSupportDirectory();` -{% endhint %} +#### API Key setup -{% hint style="info" %} -`AtEnv` comes from [at\_app\_flutter](https://pub.dev/packages/at\_app\_flutter) which also comes in the preconfigured app skeleton from [at\_app](https://pub.dev/packages/at\_app). -{% endhint %} +To get an API key for your app, first head to the [registrar website](https://my.atsign.com/dashboard), then click manage under one of your atSigns: -**API Docs** +
-You can find the API reference for the entire package available on [pub](https://pub.dev/documentation/at\_onboarding\_flutter/latest/). +Then open the Advanced settings drop down and click `Generate New API Key`: -The `AtOnboarding` class API reference is available [here](https://pub.dev/documentation/at\_onboarding\_flutter/latest/at\_onboarding/AtOnboarding-class.html). +
+{% endtab %} +{% tab title="C" %} +## C -{% endtab %} -{% endtabs %} +### 1. Fetch your atServer's address from the production atDirectory + +First, include `atclient_utils.h` and `#include ` at the top of you program. + +```c +#include +#include +``` + +Next, initialize variables that will **hold** the host and port of our atServer. We will pass pointers to these variables to the function that will populate these values for us. + +```c +char *atserver_host = NULL; +int atserver_port = 0; +``` + +Lastly, use the `atclient_utils_find_atserver_address` function to populate our `atserver_host` and `atserver_port` variables. This function returns an `int` for error handling. A non-zero exit code indicates an error. + +```c +if(atclient_utils_find_atserver_address( + ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, + ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, + ATSIGN, + &atserver_host, + &atserver_port) != 0) { + // an error occurred +} +``` + +Don't forget to free the `atserver_host` variable! It is mentioned in the documentation of the `atclient_utils_find_atserver_address` function that it is the responsibility of the caller to free this variable. + +```c +free(atserver_host); +``` + +### 2. Load your atSign's atKeys + +First, include `atkeys.h`. + +```c +#include +``` + +Next, allocate memory for the `atclient_atkeys` struct. In this scenario, we are statically allocating. We need to call the `atclient_atkeys_init` function and pass a pointer to our `atclient_atkeys` struct. You will find this to be a common pattern when working with C structs. This is our method of mimicking object-oriented methodology in our C SDK. + +```c +atclient_atkeys atkeys; +atclient_atkeys_init(&atkeys); +``` + +Finally, call the `atclient_utils_populate_atkeys_from_homedir` function which will look for your `ATSIGN`'s keys in your `$HOME/.atsign/keys/` directory. + +For example, if my atSign was `@alice`, I would have my `@alice` keys set up such that this file exists: `$HOME/.atsign/keys/@alice_key.atKeys.` + +This function returns an `int` for error handling purposes. A non-zero exit code indicates that an error has occurred. + +```c +if (atclient_utils_populate_atkeys_from_homedir( + &atkeys, + ATSIGN) != 0) +{ + // an error occurred +} +``` + +Don't forget to run `atclient_atkeys_free`at the end of your application. You will see it as a common pattern that for every `*_init` function that we call, we must have a corresponding `*_free` function. +```c +atclient_atkeys_free(&atkeys); +``` + +### 3. PKAM Authenticate + +If your atSign's keys exist and your atSign's atServer is already activated, then this is known as pkam authentication. + +First include `atclient.h` at the top of your application. + +```c +#include +``` + +Secondly, we will create our `atclient` object. This variable holds data necessary for doing various operations on our atServer later on (such as CRUD or Events). + +```c +atclient atclient; +atclient_init(&atclient); +``` +Thirdly, call the `atclient_pkam_authenticate` function. + +This function connects to the atServer (if necessary) and uses your atSign's atKeys file to generate a special pkam command by signing a challenge sent from the atServer. Once pkam authentication is successful, your connection will be authenticated and your client will be free to make various operations on the atServer. + +This function will return an `int` for error handling, in which a non-zero exit code indicates an error. + +````c +``` +if (atclient_pkam_authenticate( + &atclient, + atserver_host, + atserver_port, + &atkeys, + ATSIGN) != 0) +{ + // error occurred +} +``` +```` + +Lastly, do not forget to call `atclient_free` at the end of your application. + +```c +atclient_free(&atclient); +``` + +### Example Application + +Here is an example application that authenticates my atSign `@jeremy_0`. + +```c +#include +#include +#include +#include +#include + +#define ATSIGN "@jeremy_0" + +int main() +{ + int exit_code = -1; + + /* + * this function will print DEBUG logs and below + */ + atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG); + + /* + * these variables will hold the output of the atclient_utils_find_atserver_address function + */ + char *atserver_host = NULL; + int atserver_port = 0; + + /* + * the `atkeys` variable will hold the encryption keys that the atClient will use to do various things like authentication and end-to-end encryption. + * these keys are typically located in `~/.atsign/keys/` and would have been generated by you for each atSign using at_activate. + * It is important to call the `_init` function before using the struct. + */ + atclient_atkeys atkeys; + atclient_atkeys_init(&atkeys); + + /* + * the `atclient` variable will hold the state of the atClient and is used to interact with the atServer. + * you will need to pass this context to most atclient functions + * It is important to call the `_init` function before using the struct. + */ + atclient atclient; + atclient_init(&atclient); + + /* + * this function will find the atServer's address from the atDirectory + * and populate the `atserver_host` and `atserver_port` variables + * with the atServer's address and port. + * Don't forget to free the `atserver_host` variable after use, when using this function. + */ + if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0) + { + goto exit; + } + + /* + * my keys are assumed to be set up in ~/.atsign/keys/@soccer99_key.atKeys + * this function will read the keys from the file and populate the `atkeys` variable + */ + if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0) + { + goto exit; + } + + /* + * this function will connect to the atServer, if it is not already connected, + * then authenticate to the atServer and establish an authenticated connection + * using the populated `atkeys` variable. + */ + if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + atlogger_log("my_first_c_app", ATLOGGER_LOGGING_LEVEL_INFO, "Authenticated to atServer successfully!\n"); + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + return exit_code; +} +} +``` +{% endtab %} +{% endtabs %} diff --git a/docs/learn/sdk/synchronization.md b/docs/learn/sdk/synchronization.md deleted file mode 100644 index 0ff73a8..0000000 --- a/docs/learn/sdk/synchronization.md +++ /dev/null @@ -1,47 +0,0 @@ -# Synchronization - -{% tabs %} -{% tab title="Flutter / Dart" %} -In Dart, the AtClient is stored within the AtClientManager. Once an atSign has been [onboarded](onboarding.md), you will be able to access the AtClientManager for its associated atSign. - -### AtClientManager - -AtClientManager is a [singleton](https://en.wikipedia.org/wiki/Singleton\_pattern) model. When `AtClientManager.getInstance()` is called, it will get the AtClientManager instance for the last onboarded atSign. - -```dart -AtClientManager atClientManager = AtClientManager.getInstance(); -``` - -{% hint style="info" %} -If you need simultaneous access to multiple atClients, you need to create a new [isolate](https://dart.dev/language/concurrency#how-isolates-work) for each additional atClient, and onboard its atSign within the isolate. - -An example of this pattern can be found in [at\_daemon\_server](https://github.com/atsign-foundation/at\_services/tree/trunk/packages/at\_daemon\_server/lib/src/server). -{% endhint %} - -### SyncService - -SyncService syncs the client app and remote secondary server’s changes: - -* If the client app’s changes are ahead, it pushes the changes to the remote secondary. -* If the remote secondary is ahead, it pulls the changes to the client app. - -You can retrieve the SyncService from the AtClientManager like so: - -```dart -SyncService syncService = atClientManager.syncService; -``` - -### Performing Syncs - -To simply issue a sync, call the `.sync` function: -{% endtab %} - -{% tab title="Other Clients" %} -At this time, other clients do not support local persistence of the atServer, and thus do not require synchronization. - -Future enhancements may enable support for your desired platform. If you require support, please file an issue on the appropriate GitHub repository: - -* [Java](https://github.com/atsign-foundation/at\_java) -* [C/C++](https://github.com/atsign-foundation/at\_c) -{% endtab %} -{% endtabs %} diff --git a/docs/sdk/get-started.md b/docs/sdk/get-started.md new file mode 100644 index 0000000..b656c63 --- /dev/null +++ b/docs/sdk/get-started.md @@ -0,0 +1,427 @@ +--- +description: Setup the atSDK for your preferred language +--- + +# Get Started + +{% tabs %} +{% tab title="Dart" %} +## Starter Dart App + +Guide coming soon! +{% endtab %} + +{% tab title="Flutter" %} +## Starter Flutter App + +_Create a Flutter application initialized for use with the atSDK_ + +{% hint style="warning" %} +If you don't have a Flutter setup for development yet, please see the [Flutter docs](https://docs.flutter.dev/get-started). +{% endhint %} + +**1. Create a new Flutter app** + +First, generate a new Flutter app (replace `at_app` with the name of your application). + +```sh +flutter create at_app +cd at_app +``` + +#### 2. Add dependencies + +Then add the following dependencies to your project: + +```sh +flutter pub add at_client_mobile at_onboarding_flutter flutter_dotenv path_provider +``` + +#### 3. Replace main.dart + +Now copy the contents of `main.dart` from below into `lib/main.dart` into your project. + +
+ +main.dart + +```dart +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:path_provider/path_provider.dart' + show getApplicationSupportDirectory; + +import 'home_screen.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(); + runApp(const MyApp()); +} + +Future loadAtClientPreference() async { + var dir = await getApplicationSupportDirectory(); + + return AtClientPreference() + ..rootDomain = 'root.atsign.org' + ..namespace = dotenv.env['NAMESPACE'] + ..hiveStoragePath = dir.path + ..commitLogPath = dir.path + ..isLocalStoreRequired = true; + // * By default, this configuration is suitable for most applications + // * In advanced cases you may need to modify [AtClientPreference] + // * Read more here: https://pub.dev/documentation/at_client/latest/at_client/AtClientPreference-class.html +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + @override + MyAppState createState() => MyAppState(); +} + +class MyAppState extends State { + // * load the AtClientPreference in the background + Future futurePreference = loadAtClientPreference(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + // * The onboarding screen (first screen) + home: Scaffold( + appBar: AppBar( + title: const Text('MyApp'), + ), + body: Builder( + builder: (context) => Center( + child: ElevatedButton( + onPressed: () async { + AtOnboardingResult onboardingResult = + await AtOnboarding.onboard( + context: context, + config: AtOnboardingConfig( + atClientPreference: await futurePreference, + rootEnvironment: RootEnvironment.Production, + ), + ); + if (mounted) { + switch (onboardingResult.status) { + case AtOnboardingResultStatus.success: + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const HomeScreen()), + ); + break; + case AtOnboardingResultStatus.error: + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + backgroundColor: Colors.red, + content: Text('An error has occurred'), + ), + ); + break; + case AtOnboardingResultStatus.cancel: + break; + } + } + }, + child: const Text('Onboard an @sign'), + ), + ), + ), + ), + ); + } +} + +``` + +
+ +#### 4. Add home\_screen.dart + +Create a second file in `lib/` called `home_screen.dart`. Copy the contents of `home_screen.dart` from below into your project. + +
+ +home_screen.dart + +```dart +import 'package:at_client_mobile/at_client_mobile.dart'; +import 'package:flutter/material.dart'; + +// * Once the onboarding process is completed you will be taken to this screen +class HomeScreen extends StatelessWidget { + const HomeScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // * Getting the AtClientManager instance to use below + AtClientManager atClientManager = AtClientManager.getInstance(); + // * Getting the AtClient instance to use below + AtClient atClient = atClientManager.atClient; + + // * From this widget onwards, you are fully onboarded! + // * You can use the AtClient instance to perform operations as the onboared atSign + return Scaffold( + appBar: AppBar( + title: const Text('What\'s my current @sign?'), + ), + body: Center( + child: Column(children: [ + const Text('Successfully onboarded and navigated to FirstAppScreen'), + + // * Use the AtClient to get the current @sign + Text('Current @sign: ${atClient.getCurrentAtSign()}') + ]), + ), + ); + } +} + +``` + +
+ +#### 5**a. gitignore the .env file** + +Add the following line to the top of `.gitignore`: + +``` +.env +``` + +This will prevent accidentally uploading the secret values stored in `.env` to any git repositories associated with your app. + +**5b. Add and populate the .env file** + +Create a new file in the root of the project (next to `pubspec.yaml`) called `.env`. Add the following lines to it: + +```sh +NAMESPACE= +API_KEY= +``` + +These values should be populated with your unique app namespace, and registrar API key. + +**5.c. Register the .env file in pubspec.yaml** + +In `pubspec.yaml` there should be a `flutter:` key, under this key, we want to create and add `.env` the `assets:` list: + +```yaml +... +flutter: + ... + assets: + - .env + ... +... +``` + +#### 6. Start developing! + +Start your application with the following command: + +```sh +flutter run +``` + +Now you are ready to begin developing! +{% endtab %} + +{% tab title="C" %} +## Get Started C Application + +Setup a CMake project which includes the atSDK package suite. + +#### 1. Ensure you have a C Compiler, CMake, and a build automation tool + +The method at which you decide to use to install these prerequisites are up to you. These three tools can be installed using either `apt` or `pip`. + +Firstly, ensure you have a C compiler such as [clang](https://clang.llvm.org/) or [gcc](https://gcc.gnu.org/). We recommend [gcc](https://gcc.gnu.org/) because that is currently what Atsign is focused on supporting. + +```bash +gcc --version +``` + +Secondly, ensure you have [CMake](https://cmake.org/) installed using `cmake --version`. Ensure that your CMake version is >= 3.24. If you have issues obtaining a newer version of CMake, we have found lots of success using `pip` when installing CMake via `pip install cmake@3.24` + +```bash +cmake --version +``` + +Lastly, ensure you have a automation tool installed, such as [GNU Make](https://www.gnu.org/software/make/). We recommend [GNU Make](https://www.gnu.org/software/make/) because that is currently what Atsign is focused on supporting. These examples are using GNU Make version 4.3, but any version should work. + +```bash +make --version +``` + +#### 2. Create an empty directory + +This command will create a directory and then go into it. + +```sh +mkdir my_first_c_app && cd my_first_c_app +``` + +#### 3. Create CMakeLists.txt + +Inside your project folder, create a new file called `CMakeLists.txt.` + +Copy and paste the CMake code into the file. Feel free to change the project name by changing this line:`project(my_first_c_app)` . + +```cmake +# Minimum cmake version required by atSDK is 3.24 +cmake_minimum_required(VERSION 3.24) + +project(my_first_c_app) + +include(FetchContent) + +FetchContent_Declare( + atclient + URL https://github.com/atsign-foundation/at_c/releases/download/v0.1.0/at_c-v0.1.0.tar.gz + URL_HASH SHA256=494a4960dedc45484a0078db4c388cd592efdebde45e40bdded5c2b9c587b146 + SOURCE_SUBDIR packages/atclient +) + +FetchContent_MakeAvailable(atclient) + +add_executable(main ${CMAKE_CURRENT_LIST_DIR}/main.c) + +target_link_libraries(main PRIVATE atclient atlogger atchops mbedtls mbedcrypto mbedx509 cjson) +``` + +#### 3. Create main.c + +Inside your project folder, create a new file called `main.c` + +Change the line `#define ATSIGN "@jeremy_0"` to the atSign that you have keys to. For example, if you own an atSign `@alice` and have its keys in the correct directory `~/.atsign/keys/@alice_key.atKeys`, then I would change this line in my code to `#define ATSIGN "@alice"` + +```c +#include +#include +#include +#include +#include + +#define ATSIGN "@jeremy_0" + +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 atclient; + atclient_init(&atclient); + + 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(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0) + { + goto exit; + } + + atlogger_log("my_first_c_app", ATLOGGER_LOGGING_LEVEL_INFO, "Authenticated to atServer successfully!\n"); + + exit_code = 0; +exit: +{ + free(atserver_host); + atclient_atkeys_free(&atkeys); + atclient_free(&atclient); + return exit_code; +} +} +``` + +#### 4. CMake Configure + +In the terminal, run the following command. + +```bash +cmake -S . -B build +``` + +This is known as the "configure" step where CMake will build instructions on how to build the app specific to your machine. + +Output should be similar to the following: + +```bash +$ cmake -S . -B build +-- [ATSDK] ATSDK_AS_SUBPROJECT: ON +-- Building atlogger +-- [ATLOGGER] ATLOGGER_AS_SUBPROJECT: ON +-- Building atchops +-- [ATCHOPS] ATCHOPS_AS_SUBPROJECT: ON +-- [MbedTLS] fetching package... +-- [uuid4] fetching package... +-- Building atclient +-- [ATCLIENT] ATCLIENT_AS_SUBPROJECT: ON +-- [cjson] fetching package... +-- Configuring done +-- Generating done +-- Build files have been written to: /home/jeremy/GitHub/at_demos/demos/get_started_c/1-authentication/build +``` + +#### 5. Build + +Use CMake to build your configure and build your programs: + +```bash +cmake --build build +``` + +This command is the same as running `cd build && make all` (if you are using Makefiles). + +Your output will look similar to: + +``` +cmake --build build +[ 50%] Building C object CMakeFiles/main.dir/main.c.o +[100%] Linking C executable main +[100%] Built target main +``` + +#### 6. Run the binary + +The previous command, if ran successfully, would have created a binary executable for you: `./build/main`. + +Simply run the program: + +```bash +./build/main +``` + +Your output will look similar to: + +```bash +[DEBG] 2024-08-15 00:37:16.517243 | connection | SENT: "jeremy_0" +[DEBG] 2024-08-15 00:37:16.550027 | connection | RECV: "3b419d7a-2fee-5080-9289-f0e1853abb47.swarm0002.atsign.zone:5770" +[DEBG] 2024-08-15 00:37:16.892252 | connection | SENT: "from:jeremy_0" +[DEBG] 2024-08-15 00:37:16.929632 | connection | RECV: "data:_d6d1a569-b944-4fd1-8c1e-7fc26e508452@jeremy_0:75be9182-bfdc-437f-96bc-c45eb5a1c0ff" +[DEBG] 2024-08-15 00:37:16.968986 | connection | SENT: "pkam:QLuCl8JITCbOF6J8A6WlqBX3FUKoRR8DHh0FMnbVhHYRiuXZdBHJks0aQtq2cUj47KdhJCvN3Uk728UJHzwLgIF00wE3893xUi+9luoD7OFdV4Pbtmrxvj4K/s81A4Z9XBEJwtKerWStUavX5hR39By6Y6NJ2HeCuqVvBw2WQ/gKM15cAndpXrYzEVNJk9eCCN4+VXWxJOm6FhoY41Qxn/QSYqnRqp6PfHiY1rFXjsikJX6ip3LrCFTabJAS78BHx4BmmUzGKVPn0dFtYwvEKBgumViQhKHwS1ae1xBkLTZCARdIUhdhudoyba/ogwIEMjTWanLzAbqtNRa6qPTDrw==" +[DEBG] 2024-08-15 00:37:17.018977 | connection | RECV: "data:success" +[INFO] 2024-08-15 00:37:17.019891 | my_first_c_app | Authenticated to atServer successfully! +``` + +Congratulations! You have successfully ran a barebones Atsign C application. +{% endtab %} +{% endtabs %} diff --git a/docs/sdk/synchronization/README.md b/docs/sdk/synchronization/README.md new file mode 100644 index 0000000..7ecd5cb --- /dev/null +++ b/docs/sdk/synchronization/README.md @@ -0,0 +1,9 @@ +--- +description: Implementation specific features to know about +--- + +# Additional Features + +Some atSDKs have additional features, usually due to some technical reason. You can find a list of those features and which platforms/implementations support them here. + +
FeatureWhat it doesPlatform(s) Supported
synchronization.mdOffline support for appsFlutter / Dart
connection-hooks.mdControl the connection lifecycleC
diff --git a/docs/sdk/synchronization/connection-hooks.md b/docs/sdk/synchronization/connection-hooks.md new file mode 100644 index 0000000..44f09ca --- /dev/null +++ b/docs/sdk/synchronization/connection-hooks.md @@ -0,0 +1,173 @@ +# Connection Hooks + +{% tabs %} +{% tab title="C" %} +## C + +### Connection Hooks + +Connections hooks are useful for when you want to add additional control to the connection lifecycle in the atSDK. This means you as the developer can implement things like automatically reconnecting when connections drop (example below). + +### Usage + +#### 1. Enable hooks in your context's atserver\_connection + +Since not every connection uses hooks, you must explicitly enable it. + +```c +atclient_connection_hooks_enable(&(atclient->atserver_connection)); +``` + +#### 2. Write a void\* function + +Write a function that we would like our atclient connection to run at certain times during the program. + +```c +void *pre_write_hook(atclient_connection_hook_params *params) +{ + atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "pre_write_hook called\n"); + return NULL; +} +``` + +#### 3. Set the hook + +For example, we can set the `ATCLIENT_CONNECTION_HOOK_TYPE_PRE_WRITE` hook to run our function `pre_write_hook` before we write any data to our atServer connection. + +```c +atclient_connection_hooks_set(&(atclient->atserver_connection), ATCLIENT_CONNECTION_HOOK_TYPE_PRE_WRITE, pre_write_hook)) +``` + +### Examples + +#### Print something before we write to the atServer + +```c +#include +#include +#include +#include +#include +#include +#include + +#define TAG "5a-hooks" + +#define ATSIGN "@soccer0" + +void *pre_write_hook(atclient_connection_hook_params *params) +{ + atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "pre_write_hook called\n"); + return NULL; +} + +int setup_hooks(atclient *atclient) +{ + int exit_code; + + if ((exit_code = atclient_connection_hooks_enable(&(atclient->atserver_connection))) != 0) + { + return exit_code; + } + + if ((exit_code = atclient_connection_hooks_set(&(atclient->atserver_connection), ATCLIENT_CONNECTION_HOOK_TYPE_PRE_WRITE, pre_write_hook)) != 0) + { + return exit_code; + } + + exit_code = 0; + return exit_code; +} + +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; + } + + /* + * Set up hooks so that we can run code before our connection writes to the atServer. + */ + if ((exit_code = setup_hooks(&atclient1)) != 0) + { + goto exit; + } + atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Hooks set up successfully.\n"); + + /* + * Now at this point, it's business as normal. + * For the sake of this example, we're going to send a notification. + * Since we set up a pre write hook, it will log a message before we send any data to the atServer. + */ + 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; + } + + const char *value = "Hello! This is a notification!"; + if ((exit_code = atclient_notify_params_set_value(¬ify_params, value)) != 0) + { + goto exit; + } + + 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; +} +} +``` + +#### Automatically reconnect when the connection drops + +```c +// TODO +``` +{% endtab %} +{% endtabs %} + diff --git a/docs/sdk/synchronization/synchronization.md b/docs/sdk/synchronization/synchronization.md new file mode 100644 index 0000000..a12acdf --- /dev/null +++ b/docs/sdk/synchronization/synchronization.md @@ -0,0 +1,24 @@ +# Synchronization + +{% tabs %} +{% tab title="Flutter / Dart" %} +## Flutter / Dart + +### SyncService + +SyncService syncs the client app and remote secondary server’s changes: + +* If the client app’s changes are ahead, it pushes the changes to the remote secondary. +* If the remote secondary is ahead, it pulls the changes to the client app. + +### Usage + +To trigger a sync, call the `.sync` function: + +```dart +AtClientManager atClientManager = AtClientManager.getInstance(); +SyncService syncService = atClientManager.syncService; +syncService.sync(); +``` +{% endtab %} +{% endtabs %} diff --git a/docs/tutorials/flutter-and-atsdk-app.md b/docs/tutorials/flutter-and-atsdk-app.md deleted file mode 100644 index dd9386a..0000000 --- a/docs/tutorials/flutter-and-atsdk-app.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -description: Create a Flutter application initialized for use with the atSDK ---- - -# Flutter & atSDK app - -#### **1. Create a new Flutter app** - -First, generate a new Flutter app (replace `at_app` with the name of your application). - -```sh -flutter create at_app -cd at_app -``` - -#### 2. Add dependencies - -Then add the following dependencies to your project: - -```sh -flutter pub add at_client_mobile at_onboarding_flutter flutter_dotenv path_provider -``` - -#### 3. Replace main.dart - -Now copy the contents of `main.dart` from below into `lib/main.dart` into your project. - -
- -main.dart - -```dart -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:path_provider/path_provider.dart' - show getApplicationSupportDirectory; - -import 'home_screen.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await dotenv.load(); - runApp(const MyApp()); -} - -Future loadAtClientPreference() async { - var dir = await getApplicationSupportDirectory(); - - return AtClientPreference() - ..rootDomain = 'root.atsign.org' - ..namespace = dotenv.env['NAMESPACE'] - ..hiveStoragePath = dir.path - ..commitLogPath = dir.path - ..isLocalStoreRequired = true; - // * By default, this configuration is suitable for most applications - // * In advanced cases you may need to modify [AtClientPreference] - // * Read more here: https://pub.dev/documentation/at_client/latest/at_client/AtClientPreference-class.html -} - -class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); - @override - MyAppState createState() => MyAppState(); -} - -class MyAppState extends State { - // * load the AtClientPreference in the background - Future futurePreference = loadAtClientPreference(); - - @override - Widget build(BuildContext context) { - return MaterialApp( - // * The onboarding screen (first screen) - home: Scaffold( - appBar: AppBar( - title: const Text('MyApp'), - ), - body: Builder( - builder: (context) => Center( - child: ElevatedButton( - onPressed: () async { - AtOnboardingResult onboardingResult = - await AtOnboarding.onboard( - context: context, - config: AtOnboardingConfig( - atClientPreference: await futurePreference, - rootEnvironment: RootEnvironment.Production, - domain: 'root.atsign.org', - ), - ); - if (mounted) { - switch (onboardingResult.status) { - case AtOnboardingResultStatus.success: - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const HomeScreen()), - ); - break; - case AtOnboardingResultStatus.error: - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - backgroundColor: Colors.red, - content: Text('An error has occurred'), - ), - ); - break; - case AtOnboardingResultStatus.cancel: - break; - } - } - }, - child: const Text('Onboard an @sign'), - ), - ), - ), - ), - ); - } -} - -``` - -
- -#### 4. Add home\_screen.dart - -Create a second file in `lib/` called `home_screen.dart`. Copy the contents of `home_screen.dart` from below into your project. - -
- -home_screen.dart - -```dart -import 'package:at_client_mobile/at_client_mobile.dart'; -import 'package:flutter/material.dart'; - -// * Once the onboarding process is completed you will be taken to this screen -class HomeScreen extends StatelessWidget { - const HomeScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - // * Getting the AtClientManager instance to use below - AtClientManager atClientManager = AtClientManager.getInstance(); - // * Getting the AtClient instance to use below - AtClient atClient = atClientManager.atClient; - - // * From this widget onwards, you are fully onboarded! - // * You can use the AtClient instance to perform operations as the onboared atSign - return Scaffold( - appBar: AppBar( - title: const Text('What\'s my current @sign?'), - ), - body: Center( - child: Column(children: [ - const Text('Successfully onboarded and navigated to FirstAppScreen'), - - // * Use the AtClient to get the current @sign - Text('Current @sign: ${atClient.getCurrentAtSign()}') - ]), - ), - ); - } -} - -``` - -
- -#### 5. Setup the .env file - -**5.a. gitignore the .env file** - -Add the following line to the top of `.gitignore`: - -``` -.env -``` - -This will prevent accidentally uploading the secret values stored in `.env` to any git repositories associated with your app. - -**5.b. Add and populate the .env file** - -Create a new file in the root of the project (next to `pubspec.yaml`) called `.env`. Add the following lines to it: - -```sh -NAMESPACE= -API_KEY= -``` - -These values should be populated with your unique app namespace, and registrar API key. - -**5.c. Register the .env file in pubspec.yaml** - -In `pubspec.yaml` there should be a `flutter:` key, under this key, we want to create and add `.env` the `assets:` list: - -```yaml -... -flutter: - ... - assets: - - .env - ... -... -``` - -#### 6. Start developing! - -Start your application with the following command: - -```sh -flutter run -``` - -Now you are ready to begin developing! - -### Additional Notes - -#### Onboarding - -To authenticate an atSign, we will use `at_onboarding_flutter`. You can register an atSign [through the registrar](https://my.atsign.com/go) or through the widget in the app. - -```dart -AtOnboardingResult onboardingResult = await AtOnboarding.onboard( - context: context, - config: AtOnboardingConfig( - atClientPreference: futurePreference, - rootEnvironment: RootEnvironment.Production, - appAPIKey: dotenv.env['API_KEY'], - domain: 'root.atsign.org', - ), -); -``` - -To get an API key for your app you can get on by going to the Manage section of your atSign on the [registrar website](https://my.atsign.com/dashboard) and then click Advanced Settings. - -

Manage your atSign

- -

Get an API Key

- -## Other atPlatform Packages - -View other packages from Atsign [here](https://pub.dev/publishers/atsign.org/packages)