https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-chat-application-with-angular-material-and-vonage/angular_chat-app.png

Construire une application de chat avec Angular Material et Vonage

Publié le November 6, 2020

Temps de lecture : 27 minutes

Note : Certains des outils ou méthodes décrits dans cet article peuvent ne plus être pris en charge ou ne plus être d'actualité. Pour un contenu mis à jour ou une assistance, consultez nos derniers articles ou contactez-nous sur le site Communauté Vonage Slack

Dans ce tutoriel, nous allons activer le chat dans une application web Angular à l'aide du JavaScript SDK et l Conversation API afin que les utilisateurs puissent communiquer dans notre application. Si vous souhaitez consulter le code source, il se trouve sur notre page GitHub Vonage Vonage Community GitHub.

C'est ce que nous essayons de construire :

Gif showing the end goal of this chat application

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Avant de commencer

Avant de commencer, vous aurez besoin de quelques éléments :

  • Une compréhension de base de Angulaire

  • Node.js installé sur votre machine.

  • Le code de l'intergiciel sur GitHub.

  • Le CLI de Vonage. Installez-le comme suit :

npm install -g @vonage/cli

Configurez le CLI pour qu'il utilise la clé et le secret de l'API de Vonage :

vonage auth set –api-key=’VONAGE_API_KEY –api-secret=’VONAGE_API_SECRET’

Obtenir le code de l'intergiciel sur GitHub

Tout d'abord, nous allons cloner le code source de l'intergiciel et installer les dépendances nécessaires. Nous allons écrire une application Node.js à l'aide d'Express qui fournit un niveau d'abstraction entre l'API Vonage et le code Angular :

git clone https://github.com/Nexmo/stitch-demo.git
cd stitch-demo
npm install

Exécuter le code de l'intergiciel à partir de GitHub

Avant de pouvoir exécuter le code, nous devons créer une application RTC au sein de la plateforme Vonage pour l'utiliser dans ce code :

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

La sortie de la commande ci-dessus ressemblera à ceci :

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

Le premier élément est l'ID de l'Application, que vous devez noter (nous l'appellerons APP_ID plus tard). La dernière valeur est l'emplacement de la clé privée. La clé privée est utilisée pour générer des JWT qui authentifient vos interactions avec Vonage.

Nous devons maintenant faire une copie de example.env et l'appeler .envet mettre à jour les valeurs dans votre système Vonage API_KEY et API_SECRET. Nous devrons également ajouter le APP_ID nous venons de générer le chemin d'accès à votre clé privée. Après avoir mis à jour les valeurs, nous pouvons exécuter le code en mode débogage avec :

npm run debug

Créer des utilisateurs et des conversations

L'application doit fonctionner sur localhost:3000. Maintenant que l'application fonctionne, nous allons créer quelques utilisateurs et une conversation, puis nous ajouterons les utilisateurs que nous avons créés à la conversation.

Nous allons créer deux utilisateurs en exécutant cette commande deux fois, une fois avec le nom d'utilisateur alice et ensuite avec jamie:

curl --request POST \ --url http://localhost:3000/api/users \ --header 'content-type: application/json' \ --data '{ "username": "alice", "admin": true }'

Le résultat devrait ressembler à ce qui suit :

{"user":{"id":"USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab","href":"http://conversation.local/v1/users/USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab"},"user_jwt":"USER_JWT"}

Nous noterons l'identifiant de l'utilisateur et nous nous y référerons plus tard en tant que USER_ID. Maintenant, créons une conversation via l'API de démonstration :

curl --request POST \ --url http://localhost:3000/api/conversations \ --header 'content-type: application/json' \ --data '{"displayName": "My Chat"}'

Le résultat devrait ressembler à ce qui suit :

{"id":"CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab","href":"http://conversation.local/v1/conversations/CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab"}

Nous noterons l'identifiant de la conversation et nous y ferons référence ultérieurement en tant que CONVERSATION_ID. Maintenant, joignons les utilisateurs à la conversation. Nous allons exécuter la commande suivante deux fois - n'oubliez pas de remplacer les balises CONVERSATION_ID et USER_ID par les ID des deux étapes précédentes à chaque fois que vous exécutez cette commande :

curl --request PUT \ --url http://localhost:3000/api/conversations \ --header 'content-type: application/json' \ --data '{ "conversationId": "CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab", "userId": "USR-aaaaaaaa-bbbb-cccc-dddd-0123456789ab", "action": "join" }'

Générer une application Angular

Maintenant que nous avons notre middleware opérationnel, il est temps de créer l'application Angular. Nous allons utiliser le Angular CLI pour générer l'application, donc si vous ne l'avez pas installé, vous devrez d'abord l'installer :

npm install -g @angular/cli

Ensuite, nous l'utiliserons pour générer une nouvelle application avec le routage. La génération de tous les fichiers et l'installation des dépendances peuvent prendre un certain temps :

ng new nexmo-stitch-angular --routing

Ajouter le Material Design

Une fois la commande précédente terminée, nous ajouterons Angular Material et ses dépendances au projet :

npm install --save @angular/material @angular/cdk @angular/animations

Nous devrons également importer le NgModule pour chaque composant que nous voulons utiliser dans notre application. Pour ce faire, nous devons ouvrir src/app/app.module.ts dans notre éditeur et importer les modules en haut :

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import {
  MatAutocompleteModule,
  MatButtonModule,
  MatButtonToggleModule,
  MatCardModule,
  MatCheckboxModule,
  MatChipsModule,
  MatDatepickerModule,
  MatDialogModule,
  MatExpansionModule,
  MatGridListModule,
  MatIconModule,
  MatInputModule,
  MatListModule,
  MatMenuModule,
  MatNativeDateModule,
  MatPaginatorModule,
  MatProgressBarModule,
  MatProgressSpinnerModule,
  MatRadioModule,
  MatRippleModule,
  MatSelectModule,
  MatSidenavModule,
  MatSliderModule,
  MatSlideToggleModule,
  MatSnackBarModule,
  MatSortModule,
  MatTableModule,
  MatTabsModule,
  MatToolbarModule,
  MatTooltipModule,
  MatStepperModule
} from '@angular/material';

Nous devons également mettre à jour la déclaration @NgModules pour y ajouter les modules mentionnés ci-dessus :

@NgModule({
  ...
  imports: [
    imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpClientModule,
    MatTabsModule,
    MatCardModule,
    MatGridListModule,
    MatButtonModule,
    MatInputModule,
    MatListModule,
    MatIconModule,
    MatSidenavModule,
    MatProgressSpinnerModule,
    MatTooltipModule,
    MatDialogModule
  ],
  ],
  ...
})

Nous voudrons également ajouter un thème à Material, donc ajoutez cette ligne à votre fichier style.css:

@import "~@angular/material/prebuilt-themes/indigo-pink.css";

Si nous voulons utiliser l'icône officielle Material Design Iconsnous devrons charger la police de caractères des icônes dans notre fichier index.html:

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Polyfill Vonage Client SDK pour JavaScript

Maintenant que nous avons généré notre application Angular et qu'elle est entièrement configurée avec Material, nous allons installer le Vonage Client SDK pour JavaScript et l'ajouter au bas de . polyfills.ts:

npm install --save nexmo-conversation
/**********************************************************
 * APPLICATION IMPORTS
 */
import 'nexmo-conversation';

Service de messagerie

Nous allons devoir créer un service Angular pour gérer les données de notre middleware. Générons-le en utilisant le CLI Angular :

ng g service messaging

Deux nouveaux fichiers ont été générés dans notre dossier src/app Deux nouveaux fichiers ont été générés dans notre dossier messaging.service.spec.ts et messaging.service.spec.ts. Nous allons mettre à jour le fichier messaging.service.spec.ts afin d'ajouter le fichier ConversationClient de Vonage In-App SDK, instancier un client et gérer l'obtention d'un utilisateur. Token Web JSON de l'intergiciel. Nous allons remplacer le code de base par :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

declare var ConversationClient: any;
const GATEWAY_URL = "http://localhost:3000/api/";

@Injectable()
export class MessagingService {


  constructor(private http: HttpClient) {

  }

  initialize() {
    this.client = new ConversationClient(
      {
        debug: false
      }
    )
  }

  public client: any
  public app: any


  public getUserJwt(username: string): Promise<any> {
    return this.http.get(GATEWAY_URL + "jwt/" + username + "?admin=true").toPromise().then((response: any) => response.user_jwt)
  }
}

Nous devons mettre à jour app.module.ts afin d'importer le MessagingService et l'enregistrer en tant que fournisseur :

import { MessagingService } from './messaging.service';

...
    providers: [MessagingService],
...

Composant de connexion

Commençons à construire l'interface utilisateur de notre application. Nous allons commencer par un LoginComponent, que nous allons générer avec le CLI d'Angular :

ng g component login

Cela générera un dossier login à l'intérieur du dossier app et quatre fichiers, pour le code HTML, CSS et TypeScript, ainsi que pour les tests. Remplaçons le code dans login.component.html par une interface utilisateur permettant de se connecter. J'ai choisi un fichier <mat-grid-list> avec un <mat-card> à l'intérieur, et un formulaire avec un bouton de connexion qui appelle onLogin() lorsqu'il est soumis. Le code ressemble à ceci :

<mat-grid-list cols="4" rowHeight="100px">
  <mat-grid-tile colspan="1" rowspan="5"></mat-grid-tile>
  <mat-grid-tile colspan="2" rowspan="1"></mat-grid-tile>
  <mat-grid-tile colspan="1" rowspan="5"></mat-grid-tile>
  <mat-grid-tile colspan="2" rowspan="3">
    <mat-card class="mat-typography login">
      <h1>Login</h1>
      <form (ngSubmit)="onLogin()">
        <mat-form-field class="full-width">
          <input matInput placeholder="Username" name="username" [(ngModel)]="username">
        </mat-form-field>
        <br>
        <button type="submit" mat-raised-button color="primary" (click)="showSpinner = !showSpinner">Login
          <mat-spinner color="accent" mode="indeterminate" *ngIf="showSpinner"></mat-spinner>
        </button>
      </form>
    </mat-card>
  </mat-grid-tile>
</mat-grid-list>

Ajoutons-y une feuille de style CSS pour la barre d'outils Material dans le fichier login.component.css dans le fichier

.login {
    text-align: center;
}

.mat-spinner {
    width: 20px !important;
    height: 20px !important;
    display: inline-block;
    margin-left: 10px;
}

/deep/ .mat-spinner svg{
    width: 20px !important;
    height: 20px !important;
}

Nous devons également mettre à jour login.component.ts afin d'y ajouter le MessagingService et mettre en œuvre la méthode onLogin() et mettre en œuvre la méthode La méthode va prendre le nom d'utilisateur, faire une demande via le service de messagerie à l'intergiciel que nous exécutons afin d'obtenir un JWT d'utilisateur, puis l'utiliser pour s'authentifier via la méthode login du SDK JavaScript In-App de Vonage. Le code ressemble à ceci :

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { MessagingService } from '../messaging.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  username: string = ""

  constructor(private ms: MessagingService, private router: Router) { }

  ngOnInit() {
    this.ms.initialize()
  }

  onLogin() {
    this.ms.getUserJwt(this.username).then(this.authenticate.bind(this))
  }

  authenticate(userJwt: string) {
    this.ms.client.login(userJwt).then(app => {
      this.ms.app = app
      this.router.navigate(['/conversation']);
    })
  }
}

Remarquez que j'ai importé le Router d'Angular, je l'ai injecté dans notre constructeur et je l'utilise à la fin du flux d'authentification pour naviguer vers la page suivante, /conversation.

Composante de conversation

Nous n'avons pas encore créé le composant conversation nous n'avons pas encore créé le composant, alors utilisons l'interface de programmation d'Angular pour le créer :

ng g component conversation

Nous allons mettre à jour le fichier conversation.component.html pour utiliser un matériau sidenav Le composant "chat" liste les conversations des utilisateurs sur la gauche et les membres de la conversation sur la droite, laissant le milieu pour notre chat principal. Nous ajouterons un en-tête à la section de discussion pour afficher le nom de la conversation et le nombre de membres, et une section de saisie sera ajoutée en bas. Nous laisserons la section du milieu pour afficher l'historique de la conversation. Nous allons construire une coquille vide pour l'instant et l'enrichir plus tard au fur et à mesure que nous développerons le fichier ConversationComponent. Le HMTL devrait ressembler à ceci :

<mat-sidenav-container class="container">
  <mat-sidenav mode="side" opened>
    <mat-card>
      <mat-tab-group>
        <mat-tab>
          <ng-template mat-tab-label>
            <mat-icon matListIcon>forum</mat-icon>
          </ng-template>
          <mat-list class="conversations">
              ...
          </mat-list>
        </mat-tab>
      </mat-tab-group>
    </mat-card>
  </mat-sidenav>
  <mat-sidenav position="end" mode="side" opened *ngIf="selectedConversation">
    <mat-card>
      <mat-list class="members">
          ...
      </mat-list>
    </mat-card>
  </mat-sidenav>
  <section class="empty-conversation" *ngIf="!selectedConversation">
    <h1 class="mat-display-1">Select a conversation from the left to start chatting</h1>
  </section>
  <section *ngIf="selectedConversation">
    <div class="mat-typography conversation-header">
        ...
    </div>
    <mat-divider></mat-divider>
    <mat-list dense class="conversation-history mat-typography">
      ...
    </mat-list>
    <div class="conversation-input">
      <mat-divider></mat-divider>
      <mat-form-field class="full-width">
        <input matInput placeholder="Start chatting..." name="text" [(ngModel)]="text">
        <mat-icon matSuffix (click)="">send</mat-icon>
      </mat-form-field>
    </div>
  </section>
</mat-sidenav-container>

Nous allons mettre à jour le fichier conversation.component.ts avec le modèle de base nécessaire pour les méthodes que nous utiliserons plus tard :

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/map';

import { MessagingService } from '../messaging.service';

@Component({
  selector: 'app-conversation',
  templateUrl: './conversation.component.html',
  styleUrls: ['./conversation.component.css']
})
export class ConversationComponent implements OnInit {

  constructor(private ms: MessagingService, private router: Router) { }

  buildConversationsArray(conversations) {
  }

  ngOnInit() {
  }

  selectConversation(conversationId: string) {
  }

  sendText(text: string) {
  }

  conversations: any
  selectedConversation: any
  text: string
  events: Array<any> = []
}

Commençons par implémenter ngOnInit() pour qu'il vérifie si nous avons des données d'application avant d'essayer d'utiliser getConversations d'utiliser le SDK In-App. S'il n'y a pas de données d'application, nous serons redirigés vers l'écran de connexion :

  ngOnInit() {
    if (!this.ms.app) {
      this.router.navigate(['/']);
    } else {
      this.ms.app.getConversations().then(conversations => {
        this.conversations = this.buildConversationsArray(conversations)
      })
    }
  }

Nous devons mettre en œuvre une méthode d'aide pour construire un tableau de conversations à partir du dictionnaire de conversations fourni par le SDK JavaScript In-App afin de pouvoir l'utiliser avec *ngFor dans l'interface utilisateur :

buildConversationsArray(conversations) {
    let array = [];

    for (let conversation in conversations) {
      array.push(conversations[conversation]);
    }

    return array
  }

Ajoutons une méthode pour sélectionner une conversation dans la liste. Nous devrons récupérer l'identifiant de la vue, le transmettre au contrôleur, puis utiliser le SDK de Vonage pour obtenir des données sur la conversation. Nous stockerons ces données dans une propriété de classe afin qu'elles puissent être affichées ultérieurement. Nous utilisons également Observable pour créer un tableau à partir de conversation.events Map afin de pouvoir recréer l'historique des conversations lorsque l'utilisateur revient dans l'application. Nous allons également ajouter un écouteur d'événement en utilisant le SDK pour écouter les événements text et les ajouter à l'historique de l'événement :

selectConversation(conversationId: string) {
    this.ms.app.getConversation(conversationId).then(conversation => {
      this.selectedConversation = conversation

      Observable.from(conversation.events.values()).subscribe(
        event => {
          this.events.push(event)
        }
      )

      this.selectedConversation.on("text", (sender, message) => {
        this.events.push(message)
      })

      console.log("Selected Conversation", this.selectedConversation)
    }
    )
  }

Enfin, ajoutons une méthode qui prend l'entrée de la vue et l'envoie à l'API In-App de Vonage via le SDK :

  sendText(text: string) {
    this.selectedConversation.sendText(text).then(() => this.text = "")
  }

Maintenant que nous avons implémenté toutes les méthodes dont nous avons besoin, nous pouvons revenir en arrière et étoffer la vue pour utiliser les modèles de données que nous avons créés dans le contrôleur. Tout d'abord, mettons à jour la section conversations dans conversation.component.html:

...
          <mat-list class="conversations">
            <mat-list-item *ngFor="let conversation of conversations" (click)="selectConversation(conversation.id)">
              <mat-icon matListIcon>forum</mat-icon>
              <p>{{conversation.display_name}}</p>
            </mat-list-item>
          </mat-list>
...

Ajoutons maintenant la section des membres :

...
      <mat-list class="members">
        <mat-list-item *ngFor="let member of selectedConversation.members | keys">
          <p>{{member.value.user.name}}</p>
        </mat-list-item>
      </mat-list>
...

Nous utilisons un tuyau appelé keys pour transformer l'objet members que nous obtenons du SDK en un tableau, nous devrons donc le créer en utilisant l'interface de programmation Angular et mettre à jour le fichier keys.pipe.ts généré :

ng g pipe keys
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'keys'
})
export class KeysPipe implements PipeTransform {

  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push({key: key, value: value[key]});
    }
    return keys;
  }
}

Ensuite, nous mettrons à jour la section conversation-header de la vue pour afficher le nom de la conversation sélectionnée et le nombre de membres :

...
    <div class="mat-typography conversation-header">
      <h2>
        <mat-icon>forum</mat-icon>
        {{selectedConversation.display_name}}</h2>
      <p>
        <mat-icon>account_circle</mat-icon>
        {{(selectedConversation.members | keys).length}} Members</p>
    </div>
...

Nous devons également mettre à jour la section conversation-history pour analyser les événements et recréer l'historique dans le chat. Les événements provenant du SDK In-App ont plusieurs types, nous allons donc prendre en compte certains d'entre eux, tels que member:joined et text:

...
    <mat-list dense class="conversation-history mat-typography">
      <mat-list-item *ngFor="let event of events; index as i" [dir]="event.from === selectedConversation.me.id ? 'rtl' : 'ltr'">
        <img *ngIf="event.type == 'text'" matListAvatar matTooltip="{{selectedConversation.members[event.from].user.name}}" src="https://randomuser.me/api/portraits/thumb/lego/{{i}}.jpg"
        />
        <p *ngIf="event.type == 'text'" [dir]="'ltr'">{{event.body.text}}</p>
        <p *ngIf="event.type == 'member:joined'" class="text-center">
          <b>{{selectedConversation.members[event.from].user.name}}</b> has joined the conversation</p>
      </mat-list-item>
    </mat-list>
...

Nous devrons mettre à jour la partie conversation-input pour pouvoir envoyer des messages dans la conversation :

...
    <div class="conversation-input">
      <mat-divider></mat-divider>
      <mat-form-field class="full-width">
        <input matInput placeholder="Start chatting..." name="text" [(ngModel)]="text">
        <mat-icon matSuffix (click)="sendText(text)">send</mat-icon>
      </mat-form-field>
    </div>
...

Ajoutons-y quelques feuilles de style CSS pour qu'il soit en plein écran et en position fixe. Ajoutez la feuille de style CSS suivante à conversation.component.css:

.container {
    display: flex;
    flex-direction: column;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

.mat-drawer.mat-drawer-side {
    padding: 0 5px;
}

.empty-conversation {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
}

.conversation-header h2, .conversation-header p {
    align-items: center;
    display: flex;
}

.text-center {
    text-align: center;
    width: 100%;
}

.conversation-history.mat-list {
    height: calc(100% - 180px);
    overflow-x: scroll;
    position: absolute;
    width: 100%;
}

.conversation-history.mat-list p {
    margin: 0;
}

.empty-conversation h1 {
    margin: 0;
}

.conversations .mat-list-item {
    cursor: pointer;
}

.mat-card {
    height: 100%;
    padding: 0 24px;
    overflow: scroll;
}
.conversation-input {
    position: absolute;
    bottom: 0;
    width: 100%;
    background-color: #fafafa;
}

section .mat-list .mat-list-avatar{
    width: 25px;
    height: 25px;
}

.mat-list-avatar {
    margin: 0 5px;
}

.right {
    text-align: right;
}

.full-width {
    width: 100%;
}

.full-width .mat-icon {
    cursor: pointer;
}

Enfin, nous devons mettre à jour le module de routage de l'application dans app-routing.module.ts pour que les bons itinéraires soient affichés :

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { ConversationComponent } from './conversation/conversation.component';


const routes: Routes = [
    {
        path: '',
        component: LoginComponent,
    },
    {
      path: 'conversation',
      component: ConversationComponent,
  }
];

@NgModule({
    imports: [
        RouterModule.forRoot(routes)
    ],
    exports: [
        RouterModule
    ],
    declarations: []
})
export class AppRoutingModule { }

Nous devons également remplacer l'intégralité de app.component.html par le <router-outlet> pour afficher le routeur sur la première page :

<router-outlet></router-outlet>

Lancez votre application !

Après avoir créé l'application décrite dans cet article, exécutez-la pour la voir fonctionner :

ng serve

L'application fonctionnera à l'adresse "http://localhost:4200". Je suggère d'ouvrir l'application dans deux onglets distincts, en se connectant à la fois avec alice et jamie et de commencer à se parler ! Si vous souhaitez voir l'application dans son état final, vous pouvez consulter le code source de cette application sur notre page GitHub de la communauté. Si vous voulez voir une version plus avancée de ce code, vous pouvez consulter le stitch-demo que vous avez téléchargé au début de ce billet. Il contient également un front-end Angular Material.

Quelle est la prochaine étape ?

Si vous souhaitez continuer à apprendre comment utiliser le Client SDK de Vonage pour JavaScript, consultez notre démarrage rapide, où nous vous montrons comment :

Partager:

https://a.storyblok.com/f/270183/384x384/dabe7c5397/laka.png
Alex LakatosAnciens de Vonage

Alex Lakatos est un défenseur des développeurs JavaScript pour Nexmo. Pendant son temps libre, il est bénévole chez Mozilla en tant que Tech Speaker et Reps Mentor. Développeur JavaScript travaillant sur le web ouvert, il en repousse les limites tous les jours. Lorsqu'il ne programme pas à Londres, il aime parcourir le monde, il est donc probable que vous le croisiez dans un salon d'aéroport.