Comment utiliser la bibliothèque cliente Apache Cordova pour Azure Mobile AppsHow to use Apache Cordova client library for Azure Mobile Apps

Notes

Visual Studio App Center investit dans des services nouveaux et intégrés, essentiels au développement d’applications mobiles.Visual Studio App Center is investing in new and integrated services central to mobile app development. Les développeurs peuvent utiliser les services Build, Test et Distribute pour configurer le pipeline de livraison et d’intégration continues.Developers can use Build, Test and Distribute services to set up Continuous Integration and Delivery pipeline. Une fois l’application déployée, les développeurs peuvent surveiller l’état et l’utilisation de leur application à l’aide des services Analytics et Diagnostics, et interagir avec les utilisateurs à l’aide du service Push.Once the app is deployed, developers can monitor the status and usage of their app using the Analytics and Diagnostics services, and engage with users using the Push service. Les développeurs peuvent également utiliser Auth pour authentifier leurs utilisateurs, ainsi que le service Data pour conserver et synchroniser les données d’application dans le cloud.Developers can also leverage Auth to authenticate their users and Data service to persist and sync app data in the cloud. Découvrez App Center dès aujourd'hui.Check out App Center today.

Vue d'ensembleOverview

Ce guide indique le déroulement de scénarios courants dans le cadre de l’utilisation du dernier plug-in Apache Cordova pour Azure Mobile Apps.This guide teaches you to perform common scenarios using the latest Apache Cordova Plugin for Azure Mobile Apps. Si vous ne connaissez pas Azure Mobile Apps, consultez d’abord la section Démarrage rapide d’Azure Mobile Apps pour créer un serveur principal, créer une table et télécharger un projet Apache Cordova prédéfini.If you are new to Azure Mobile Apps, first complete Azure Mobile Apps Quick Start to create a backend, create a table, and download a pre-built Apache Cordova project. Dans ce guide, nous nous concentrons sur le plug-in Apache Cordova côté client.In this guide, we focus on the client-side Apache Cordova Plugin.

Plateformes prises en chargeSupported platforms

Ce kit de développement logiciel (SDK) prend en charge Apache Cordova 6.0.0 et version ultérieure sur les appareils iOS, Android et Windows.This SDK supports Apache Cordova v6.0.0 and later on iOS, Android, and Windows devices. La prise en charge de la plate-forme est la suivante :The platform support is as follows:

  • Android API 19-24 (KitKat à Nougat).Android API 19-24 (KitKat through Nougat).
  • iOS 8.0 et versions ultérieures.iOS versions 8.0 and later.
  • Windows Phone 8.1.Windows Phone 8.1.
  • Plateforme Windows universelle.Universal Windows Platform.

Configuration et conditions préalablesSetup and prerequisites

Ce guide part du principe que vous avez créé un serveur principal avec une table.This guide assumes that you have created a backend with a table. Ce guide suppose que la table a le même schéma que les tables dans ces didacticiels.This guide assumes that the table has the same schema as the tables in those tutorials. Ce guide suppose également que vous avez ajouté le plug-in Apache Cordova à votre code.This guide also assumes that you have added the Apache Cordova Plugin to your code. Si ce n’est pas le cas, vous pouvez l’ajouter à votre projet depuis la ligne de commande :If you have not done so, you may add the Apache Cordova plugin to your project on the command line:

cordova plugin add cordova-plugin-ms-azure-mobile-apps

Pour plus d’informations sur la création de votre première application Apache Cordova, consultez la documentation officielle.For more information on creating your first Apache Cordova app, see their documentation.

Configuration d’une application Ionic v2Setting up an Ionic v2 app

Pour configurer correctement un projet Ionic v2, créez d’abord une application de base et ajoutez le plug-in Cordova :To properly configure an Ionic v2 project, first create a basic app and add the Cordova plugin:

ionic start projectName --v2
cd projectName
ionic plugin add cordova-plugin-ms-azure-mobile-apps

Ajoutez les lignes suivantes à app.component.ts pour créer l’objet client :Add the following lines to app.component.ts to create the client object:

declare var WindowsAzure: any;
var client = new WindowsAzure.MobileServiceClient("https://yoursite.azurewebsites.net");

Vous pouvez maintenant générer et exécuter le projet dans le navigateur :You can now build and run the project in the browser:

ionic platform add browser
ionic run browser

Le plug-in Azure Mobile Apps Cordova prend en charge les applications Ionic v1 et v2.The Azure Mobile Apps Cordova plugin supports both Ionic v1 and v2 apps. Seules les applications Ionic v2 nécessitent la déclaration supplémentaire pour l’objet WindowsAzure.Only the Ionic v2 apps require the additional declaration for the WindowsAzure object.

Créer une connexion clienteCreate a client connection

Créez une connexion cliente en créant un objet WindowsAzure.MobileServiceClient .Create a client connection by creating a WindowsAzure.MobileServiceClient object. Remplacez appUrl par l’URL de votre application mobile.Replace appUrl with the URL to your Mobile App.

var client = WindowsAzure.MobileServiceClient(appUrl);

Utilisation des tablesWork with tables

Pour accéder aux données ou les mettre à jour, créez une référence à la table principale.To access or update data, create a reference to the backend table. Remplacez tableName par le nom de votre table.Replace tableName with the name of your table

var table = client.getTable(tableName);

Une fois que vous disposez d’une référence de table, vous pouvez continuer à utiliser votre table :Once you have a table reference, you can work further with your table:

Procédure : Interrogation d’une référence de tableHow to: Query a table reference

Une fois que vous disposez d’une référence de table, vous pouvez l’utiliser pour rechercher des données sur le serveur.Once you have a table reference, you can use it to query for data on the server. Les requêtes sont effectuées dans un langage de type LINQ.Queries are made in a "LINQ-like" language. Pour retourner toutes les données de la table, utilisez le code suivant :To return all data from the table, use the following code:

/**
 * Process the results that are received by a call to table.read()
 *
 * @param {Object} results the results as a pseudo-array
 * @param {int} results.length the length of the results array
 * @param {Object} results[] the individual results
 */
function success(results) {
   var numItemsRead = results.length;

   for (var i = 0 ; i < results.length ; i++) {
       var row = results[i];
       // Each row is an object - the properties are the columns
   }
}

function failure(error) {
    throw new Error('Error loading data: ', error);
}

table
    .read()
    .then(success, failure);

La fonction success est appelée avec les résultats.The success function is called with the results. Ne recourez pas à for (var i in results) dans la fonction success, car cette action entraîne une itération sur les informations contenues dans les résultats quand d’autres fonctions de requête (telles que .includeTotalCount()) sont utilisées.Do not use for (var i in results) in the success function as that will iterate over information that is included in the results when other query functions (such as .includeTotalCount()) are used.

Pour plus d’informations sur la syntaxe de requête, consultez la [documentation de l’objet Query].For more information on the Query syntax, see the [Query object documentation].

Filtrage des données sur le serveurFiltering data on the server

Vous pouvez utiliser une clause where sur la référence de table :You can use a where clause on the table reference:

table
    .where({ userId: user.userId, complete: false })
    .read()
    .then(success, failure);

Vous pouvez également utiliser une fonction qui filtre l’objet.You can also use a function that filters the object. Dans ce cas, la variable this est affectée à l’objet en cours de filtrage.In this case, the this variable is assigned to the current object being filtered. Le code suivant est équivalent à l’exemple précédent sur le plan fonctionnel :The following code is functionally equivalent to the prior example:

function filterByUserId(currentUserId) {
    return this.userId === currentUserId && this.complete === false;
}

table
    .where(filterByUserId, user.userId)
    .read()
    .then(success, failure);

Pagination des donnéesPaging through data

Utilisez les méthodes take() et skip().Utilize the take() and skip() methods. Par exemple, si vous souhaitez fractionner la table en enregistrements de 100 lignes :For example, if you wish to split the table into 100-row records:

var totalCount = 0, pages = 0;

// Step 1 - get the total number of records
table.includeTotalCount().take(0).read(function (results) {
    totalCount = results.totalCount;
    pages = Math.floor(totalCount/100) + 1;
    loadPage(0);
}, failure);

function loadPage(pageNum) {
    let skip = pageNum * 100;
    table.skip(skip).take(100).read(function (results) {
        for (var i = 0 ; i < results.length ; i++) {
            var row = results[i];
            // Process each row
        }
    }
}

La méthode .includeTotalCount() est utilisée pour ajouter un champ totalCount à l’objet results.The .includeTotalCount() method is used to add a totalCount field to the results object. Le champ totalCount est rempli avec le nombre total d’enregistrements qui est retourné si aucune pagination n’est utilisée.The totalCount field is filled with the total number of records that would be returned if no paging is used.

Vous pouvez ensuite utiliser la variable pages et des boutons d’interface utilisateur pour fournir une liste de pages ; utilisez loadPage() pour charger les nouveaux enregistrements pour chaque page.You can then use the pages variable and some UI buttons to provide a page list; use loadPage() to load the new records for each page. Implémentez la mise en cache pour accélérer l’accès aux enregistrements qui ont déjà été chargés.Implement caching to speed access to records that have already been loaded.

Procédure : Renvoi de données triéesHow to: Return sorted data

Utilisez les méthodes de requête .orderBy() ou .orderByDescending() :Use the .orderBy() or .orderByDescending() query methods:

table
    .orderBy('name')
    .read()
    .then(success, failure);

Pour plus d’informations sur l’objet Query, consultez la [documentation de l’objet Query].For more information on the Query object, see the [Query object documentation].

Procédure : Insertion des donnéesHow to: Insert data

Créez un objet JavaScript avec la date appropriée et appelez table.insert() de façon asynchrone :Create a JavaScript object with the appropriate date and call table.insert() asynchronously:

var newItem = {
    name: 'My Name',
    signupDate: new Date()
};

table
    .insert(newItem)
    .done(function (insertedItem) {
        var id = insertedItem.id;
    }, failure);

Une fois l’insertion correctement effectuée, l’élément inséré est retourné avec les champs supplémentaires qui sont nécessaires pour les opérations de synchronisation.On successful insertion, the inserted item is returned with the additional fields that are required for sync operations. Mettez à jour votre propre cache avec ces informations en vue des mises à jour ultérieures.Update your own cache with this information for later updates.

Le Kit de développement logiciel (SDK) de serveur Node.js Azure Mobile Apps prend en charge le schéma dynamique à des fins de développement.The Azure Mobile Apps Node.js Server SDK supports dynamic schema for development purposes. Le schéma dynamique vous permet d’ajouter des colonnes à la table en les spécifiant dans une opération d’insertion ou de mise à jour.Dynamic Schema allows you to add columns to the table by specifying them in an insert or update operation. Nous vous recommandons de désactiver le schéma dynamique avant de déplacer votre application vers un environnement de production.We recommend that you turn off dynamic schema before moving your application to production.

Procédure : Modifier des donnéesHow to: Modify data

Comme dans le cas de la méthode .insert(), vous devez créer un objet de mise à jour, puis appeler .update().Similar to the .insert() method, you should create an Update object and then call .update(). L’objet de mise à jour doit contenir l’ID de l’enregistrement à mettre à jour, obtenu au moment de la lecture de l’enregistrement ou de l’appel de .insert().The update object must contain the ID of the record to be updated - the ID is obtained when reading the record or when calling .insert().

var updateItem = {
    id: '7163bc7a-70b2-4dde-98e9-8818969611bd',
    name: 'My New Name'
};

table
    .update(updateItem)
    .done(function (updatedItem) {
        // You can now update your cached copy
    }, failure);

Procédure : Suppression de donnéesHow to: Delete data

Pour supprimer un enregistrement, appelez la méthode .del().To delete a record, call the .del() method. Transmettez l’ID d’une référence d’objet :Pass the ID in an object reference:

table
    .del({ id: '7163bc7a-70b2-4dde-98e9-8818969611bd' })
    .done(function () {
        // Record is now deleted - update your cache
    }, failure);

Procédure : Authentification des utilisateursHow to: Authenticate users

Azure App Service prend en charge l’authentification et l’autorisation des utilisateurs de l'application par l'intermédiaire de différents fournisseurs d'identité externes : Facebook, Google, compte Microsoft et Twitter.Azure App Service supports authenticating and authorizing app users using various external identity providers: Facebook, Google, Microsoft Account, and Twitter. Vous pouvez définir des autorisations sur les tables pour limiter l'accès à certaines opérations aux seuls utilisateurs authentifiés.You can set permissions on tables to restrict access for specific operations to only authenticated users. Vous pouvez également utiliser l’identité des utilisateurs authentifiés pour implémenter des règles d’autorisation dans les scripts serveur.You can also use the identity of authenticated users to implement authorization rules in server scripts. Pour plus d'informations, consultez la page Prise en main de l’authentification .For more information, see the Get started with authentication tutorial.

Quand vous utilisez l’authentification dans une application Apache Cordova, les plug-ins Cordova suivants doivent être réunis :When using authentication in an Apache Cordova app, the following Cordova plugins must be available:

Deux flux d’authentification sont pris en charge : un flux serveur et un flux client.Two authentication flows are supported: a server flow and a client flow. Le flux serveur fournit l'authentification la plus simple, car il repose sur l'interface d'authentification Web du fournisseur.The server flow provides the simplest authentication experience, as it relies on the provider's web authentication interface. Le flux client permet une intégration approfondie avec les fonctionnalités propres aux appareils, telles que l'authentification unique, car il repose sur des Kits de développement logiciel (SDK) propres aux appareils et aux fournisseurs.The client flow allows for deeper integration with device-specific capabilities such as single-sign-on as it relies on provider-specific device-specific SDKs.

Guide pratique pour l’authentification auprès d’un fournisseur (flux serveur)How to: Authenticate with a provider (Server Flow)

Pour que Mobile Apps gère le processus d’authentification dans votre application, vous devez inscrire votre application auprès de votre fournisseur d’identité.To have Mobile Apps manage the authentication process in your app, you must register your app with your identity provider. Ensuite, dans Azure App Service, vous devez configurer l’ID d’application et le secret fournis par votre fournisseur.Then in your Azure App Service, you need to configure the application ID and secret provided by your provider. Pour plus d'informations, consultez le didacticiel Ajout de l'authentification à votre application.For more information, see the tutorial Add authentication to your app.

Une fois que vous avez inscrit votre fournisseur d'identité, appelez la méthode .login() avec le nom de votre fournisseur.Once you have registered your identity provider, call the .login() method with the name of your provider. Par exemple, pour vous connecter avec Facebook, utilisez le code suivant :For example, to sign in with Facebook use the following code:

client.login("facebook").done(function (results) {
     alert("You are now signed in as: " + results.userId);
}, function (err) {
     alert("Error: " + err);
});

Les valeurs valides pour le fournisseur sont « aad », « facebook », « google », « microsoftaccount » et « twitter ».The valid values for the provider are 'aad', 'facebook', 'google', 'microsoftaccount', and 'twitter'.

Notes

L’authentification Google ne fonctionne pour le moment pas via le flux serveur.Google Authentication does not currently work via Server Flow. Pour s’authentifier auprès de Google, vous devez utiliser une méthode gérée par le client.To authenticate with Google, you must use a client-flow method.

Dans ce cas, Azure App Service gère le flux d’authentification OAuth 2.0.In this case, Azure App Service manages the OAuth 2.0 authentication flow. Il affiche la page de connexion du fournisseur sélectionné et génère un jeton d’authentification App Service après avoir établi une connexion avec le fournisseur d’identité.It displays the sign-in page of the selected provider and generates an App Service authentication token after successful sign-in with the identity provider. La fonction de connexion, quand elle est utilisée, renvoie un objet JSON qui expose l’ID utilisateur et le jeton d’authentification App Service dans les champs userId et authenticationToken, respectivement.The login function, when complete, returns a JSON object that exposes both the user ID and App Service authentication token in the userId and authenticationToken fields, respectively. Ce jeton peut être mis en cache et réutilisé jusqu'à ce qu'il arrive à expiration.This token can be cached and reused until it expires.

Guide pratique pour l’authentification auprès d’un fournisseur (flux client)How to: Authenticate with a provider (Client Flow)

Votre application peut également contacter le fournisseur d’identité de manière indépendante, puis fournir le jeton renvoyé à App Service à des fins d’authentification.Your app can also independently contact the identity provider and then provide the returned token to your App Service for authentication. Le flux client permet de proposer l'authentification unique aux utilisateurs ou de récupérer d'autres données utilisateur auprès du fournisseur d'identité.This client flow enables you to provide a single sign-in experience for users or to retrieve additional user data from the identity provider.

Exemple de base de l’authentification socialeSocial Authentication basic example

Cet exemple utilise le SDK client Facebook pour l'authentification :This example uses Facebook client SDK for authentication:

client.login(
     "facebook",
     {"access_token": token})
.done(function (results) {
     alert("You are now signed in as: " + results.userId);
}, function (err) {
     alert("Error: " + err);
});

Cet exemple part du principe que le jeton fourni par le Kit de développement logiciel (SDK) propre au fournisseur est stocké dans une variable token.This example assumes that the token provided by the respective provider SDK is stored in the token variable.

Guide pratique pour l’obtention des informations sur l’utilisateur authentifiéHow to: Obtain information about the authenticated user

Les informations d’authentification peuvent être récupérées du point de terminaison /.auth/me à l’aide d’un appel HTTP avec une bibliothèque AJAX.The authentication information can be retrieved from the /.auth/me endpoint using an HTTP call with any AJAX library. Veillez à définir l’en-tête X-ZUMO-AUTH sur votre jeton d’authentification.Ensure you set the X-ZUMO-AUTH header to your authentication token. Le jeton d'authentification est stocké dans client.currentUser.mobileServiceAuthenticationToken.The authentication token is stored in client.currentUser.mobileServiceAuthenticationToken. Par exemple, pour utiliser l’API d’extraction :For example, to use the fetch API:

var url = client.applicationUrl + '/.auth/me';
var headers = new Headers();
headers.append('X-ZUMO-AUTH', client.currentUser.mobileServiceAuthenticationToken);
fetch(url, { headers: headers })
    .then(function (data) {
        return data.json()
    }).then(function (user) {
        // The user object contains the claims for the authenticated user
    });

L’extraction est disponible sous forme de package npm ou de téléchargement par navigateur à partir de CDNJS.Fetch is available as an npm package or for browser download from CDNJS. Vous pouvez également utiliser jQuery ou une autre API AJAX pour extraire les informations.You could also use jQuery or another AJAX API to fetch the information. Les données sont reçues sous la forme d’un objet JSON.Data is received as a JSON object.

Procédure : Configurer votre Mobile App Service pour les URL de redirection externes.How to: Configure your Mobile App Service for external redirect URLs.

Plusieurs types d’applications Apache Cordova utilisent une fonctionnalité de bouclage pour gérer les flux d’interface utilisateur OAuth.Several types of Apache Cordova applications use a loopback capability to handle OAuth UI flows. Les flux d’interface utilisateur OAuth posent des problèmes car le service d’authentification sait uniquement comment utiliser votre service par défaut.OAuth UI flows on localhost cause problems since the authentication service only knows how to utilize your service by default. Voici quelques exemples de problèmes causés par les flux d’interface utilisateur OAuth :Examples of problematic OAuth UI flows include:

  • L’émulateur Ripple.The Ripple emulator.
  • Live recharger avec Ionic.Live Reload with Ionic.
  • Exécution locale du backend mobileRunning the mobile backend locally
  • Exécution du backend mobile dans une autre instance Azure App Service que celle fournissant l’authentification.Running the mobile backend in a different Azure App Service than the one providing authentication.

Suivez ces instructions pour ajouter vos paramètres régionaux à la configuration :Follow these instructions to add your local settings to the configuration:

  1. Connectez-vous au portail AzureLog in to the Azure portal

  2. Sélectionnez Toutes les ressources ou App Services, puis cliquez sur le nom de votre application mobile.Select All resources or App Services then click the name of your Mobile App.

  3. Cliquez sur OutilsClick Tools

  4. Cliquez sur Explorateur de ressources dans le menu OBSERVER, puis cliquez sur Atteindre.Click Resource explorer in the OBSERVE menu, then click Go. Une nouvelle fenêtre ou un nouvel onglet s’ouvre.A new window or tab opens.

  5. Développez les nœuds config et authsettings pour votre site dans le volet de navigation de gauche.Expand the config, authsettings nodes for your site in the left-hand navigation.

  6. Cliquez sur ModifierClick Edit

  7. Recherchez l’élément "allowedExternalRedirectUrls".Look for the "allowedExternalRedirectUrls" element. Il peut être défini sur null ou sur un tableau de valeurs.It may be set to null or an array of values. Remplacez la valeur par la valeur suivante :Change the value to the following value:

      "allowedExternalRedirectUrls": [
          "http://localhost:3000",
          "https://localhost:3000"
      ],
    

    Remplacez les URL par les URL de votre service.Replace the URLs with the URLs of your service. Par exemple, http://localhost:3000 (pour l’exemple de service Node.js) ou http://localhost:4400 (pour le service Ripple).Examples include http://localhost:3000 (for the Node.js sample service), or http://localhost:4400 (for the Ripple service). Il s’agit seulement d’exemples d’URL. Votre situation, y compris pour les services mentionnés dans les exemples, peut être différente.However, these URLs are examples - your situation, including for the services mentioned in the examples, may be different.

  8. Cliquez sur le bouton Lecture/Écriture dans le coin supérieur droit de l’écran.Click the Read/Write button in the top-right corner of the screen.

  9. Cliquez sur le bouton vert PUT .Click the green PUT button.

Les paramètres sont alors enregistrés.The settings are saved at this point. Ne fermez pas la fenêtre du navigateur avant la fin de l’enregistrement des paramètres.Do not close the browser window until the settings have finished saving. Ajoutez également ces URL de bouclage aux paramètres de CORS pour votre App Service :Also add these loopback URLs to the CORS settings for your App Service:

  1. Connectez-vous au portail AzureLog in to the Azure portal
  2. Sélectionnez Toutes les ressources ou App Services, puis cliquez sur le nom de votre application mobile.Select All resources or App Services then click the name of your Mobile App.
  3. Le panneau Paramètres s’ouvre automatiquement.The Settings blade opens automatically. Si ce n’est pas le cas, cliquez sur Tous les paramètres.If it doesn't, click All Settings.
  4. Cliquez sur CORS sous le menu de l’API.Click CORS under the API menu.
  5. Entrez l’URL à ajouter dans la zone correspondante et appuyez sur Entrée.Enter the URL that you wish to add in the box provided and press Enter.
  6. Entrez des URL supplémentaires, si nécessaire.Enter additional URLs as needed.
  7. Cliquez sur Enregistrer pour enregistrer les paramètres.Click Save to save the settings.

L’application des nouveaux paramètres prend environ 10 à 15 secondes.It takes approximately 10-15 seconds for the new settings to take effect.

Procédure : Inscription aux notifications PushHow to: Register for push notifications

Installez le plug-in phonegap-plugin-push pour gérer les notifications Push.Install the phonegap-plugin-push to handle push notifications. Vous pouvez ajouter ce plugin facilement en exécutant la commande cordova plugin add sur la ligne de commande, ou par le biais du programme d’installation de plug-in Git dans Visual Studio.This plugin can be easily added using the cordova plugin add command on the command line, or via the Git plugin installer within Visual Studio. Le code suivant dans votre application Apache Cordova inscrit votre appareil aux notifications Push :The following code in your Apache Cordova app registers your device for push notifications:

var pushOptions = {
    android: {
        senderId: '<from-gcm-console>'
    },
    ios: {
        alert: true,
        badge: true,
        sound: true
    },
    windows: {
    }
};
pushHandler = PushNotification.init(pushOptions);

pushHandler.on('registration', function (data) {
    registrationId = data.registrationId;
    // For cross-platform, you can use the device plugin to determine the device
    // Best is to use device.platform
    var name = 'gcm'; // For android - default
    if (device.platform.toLowerCase() === 'ios')
        name = 'apns';
    if (device.platform.toLowerCase().substring(0, 3) === 'win')
        name = 'wns';
    client.push.register(name, registrationId);
});

pushHandler.on('notification', function (data) {
    // data is an object and is whatever is sent by the PNS - check the format
    // for your particular PNS
});

pushHandler.on('error', function (error) {
    // Handle errors
});

Utilisez le Kit de développement logiciel (SDK) Notification Hubs pour envoyer des notifications Push à partir du serveur.Use the Notification Hubs SDK to send push notifications from the server. N’envoyez jamais de notifications Push directement depuis les clients.Never send push notifications directly from clients. Cela risquerait de déclencher une attaque par déni de service au niveau des concentrateurs de notification ou de PNS.Doing so could be used to trigger a denial of service attack against Notification Hubs or the PNS. Le PNS pourrait bannir votre trafic en réponse à ces attaques.The PNS could ban your traffic as a result of such attacks.

Plus d’informationsMore information

Vous pouvez trouver des informations sur les API dans notre documentation sur les API.You can find detailed API details in our API documentation.