Commentaire sur les API batterie et Fluent API en JavaScript

Explorez les APIs web avancees : Battery Status API, design pattern Fluent API et Web Cryptography pour le chiffrement cote client en JavaScript.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 12 min read
Commentaire sur les API batterie et Fluent API en JavaScript

APIs Web Avancees : Battery Status, Fluent API et Web Cryptography

Introduction

Le developpement web moderne offre aux developpeurs un arsenal d’APIs puissantes qui permettent d’interagir avec le materiel, de creer des interfaces elegantes et de securiser les donnees cote client. Ces APIs, souvent meconnues, ouvrent des possibilites fascinantes pour creer des applications web plus intelligentes et plus securisees.

Dans cet article approfondi, nous allons explorer trois APIs distinctes mais complementaires :

  1. Battery Status API : Permet d’acceder aux informations sur l’etat de la batterie de l’appareil, offrant la possibilite d’adapter le comportement de l’application en fonction de l’autonomie restante.

  2. Fluent API (Method Chaining) : Un design pattern qui permet de creer des interfaces de programmation elegantes et lisibles, ou les appels de methodes peuvent etre enchaines de maniere fluide.

  3. Web Cryptography API : Une API native du navigateur qui fournit des primitives cryptographiques pour le chiffrement, le dechiffrement, le hachage et la signature numerique.

Ces trois APIs representent differentes facettes du developpement web moderne : l’interaction avec le materiel, l’architecture logicielle et la securite. Maitriser ces concepts vous permettra de creer des applications web plus sophistiquees et mieux adaptees aux besoins des utilisateurs.

Explorons chacune de ces APIs en profondeur, avec des exemples pratiques et des cas d’usage concrets.


Battery Status API

La Battery Status API permet aux applications web d’acceder aux informations concernant l’etat de la batterie de l’appareil. Cette API est particulierement utile pour les Progressive Web Apps (PWA) et les applications qui souhaitent adapter leur comportement en fonction de l’autonomie restante.

Compatibilite des navigateurs

Attention : La Battery Status API a ete deprecee dans certains navigateurs pour des raisons de confidentialite. Elle reste disponible dans Chrome et Edge, mais a ete retiree de Firefox et Safari.

NavigateurSupportNotes
ChromeOuiSupporte depuis la version 38
EdgeOuiSupporte depuis la version 79
FirefoxNonRetire depuis la version 52 (confidentialite)
SafariNonJamais supporte
OperaOuiSupporte depuis la version 25

Verification de la disponibilite

Avant d’utiliser l’API, verifiez toujours sa disponibilite :

if ('getBattery' in navigator) {
  console.log('Battery Status API disponible');
  // Utiliser l'API
} else {
  console.log('Battery Status API non supportee');
  // Fallback ou message utilisateur
}

Proprietes de l’objet BatteryManager

L’objet BatteryManager retourne par navigator.getBattery() expose quatre proprietes principales :

navigator.getBattery().then(function(battery) {
  // Niveau de charge (0.0 a 1.0)
  console.log("Niveau : " + (battery.level * 100) + "%");

  // En charge ou non (boolean)
  console.log("En charge : " + battery.charging);

  // Temps avant charge complete (secondes, Infinity si non en charge)
  console.log("Temps de charge restant : " + battery.chargingTime + "s");

  // Temps avant decharge complete (secondes, Infinity si en charge)
  console.log("Temps de decharge : " + battery.dischargingTime + "s");
});

Evenements de la Battery API

L’API expose quatre evenements pour surveiller les changements d’etat :

navigator.getBattery().then(function(battery) {

  // Changement du niveau de batterie
  battery.addEventListener('levelchange', function() {
    console.log("Nouveau niveau : " + (battery.level * 100) + "%");
  });

  // Changement d'etat de charge (branche/debranche)
  battery.addEventListener('chargingchange', function() {
    console.log("En charge : " + battery.charging);
  });

  // Changement du temps de charge restant
  battery.addEventListener('chargingtimechange', function() {
    console.log("Temps de charge : " + battery.chargingTime + "s");
  });

  // Changement du temps de decharge
  battery.addEventListener('dischargingtimechange', function() {
    console.log("Temps restant : " + battery.dischargingTime + "s");
  });
});

Cas d’usage : Mode economie d’energie

Voici un exemple complet d’implementation d’un mode economie d’energie :

class BatteryManager {
  constructor() {
    this.battery = null;
    this.lowBatteryThreshold = 0.20; // 20%
    this.criticalBatteryThreshold = 0.10; // 10%
    this.powerSavingMode = false;
    this.init();
  }

  async init() {
    if (!('getBattery' in navigator)) {
      console.warn('Battery API non disponible');
      return;
    }

    this.battery = await navigator.getBattery();
    this.setupEventListeners();
    this.checkBatteryStatus();
  }

  setupEventListeners() {
    this.battery.addEventListener('levelchange', () => {
      this.checkBatteryStatus();
    });

    this.battery.addEventListener('chargingchange', () => {
      if (this.battery.charging) {
        this.disablePowerSavingMode();
      }
    });
  }

  checkBatteryStatus() {
    const level = this.battery.level;

    if (!this.battery.charging) {
      if (level <= this.criticalBatteryThreshold) {
        this.enableCriticalMode();
      } else if (level <= this.lowBatteryThreshold) {
        this.enablePowerSavingMode();
      } else {
        this.disablePowerSavingMode();
      }
    }
  }

  enablePowerSavingMode() {
    if (this.powerSavingMode) return;

    this.powerSavingMode = true;
    console.log('Mode economie d\'energie active');

    // Reduire la frequence des animations
    document.body.classList.add('reduce-motion');

    // Desactiver les videos en lecture automatique
    document.querySelectorAll('video[autoplay]').forEach(video => {
      video.pause();
    });

    // Reduire les requetes reseau
    this.reduceNetworkRequests();

    // Notifier l'utilisateur
    this.showNotification('Mode economie d\'energie active pour preserver votre batterie');
  }

  enableCriticalMode() {
    console.log('Mode critique - Batterie tres faible');

    // Desactiver toutes les fonctionnalites non essentielles
    this.disableNonEssentialFeatures();

    this.showNotification('Batterie critique ! Connectez votre chargeur.');
  }

  disablePowerSavingMode() {
    if (!this.powerSavingMode) return;

    this.powerSavingMode = false;
    console.log('Mode economie d\'energie desactive');

    document.body.classList.remove('reduce-motion');
  }

  reduceNetworkRequests() {
    // Augmenter l'intervalle des polls
    // Desactiver les prefetch
    // Utiliser des images de resolution inferieure
  }

  disableNonEssentialFeatures() {
    // Logique specifique a l'application
  }

  showNotification(message) {
    if ('Notification' in window && Notification.permission === 'granted') {
      new Notification('Batterie', { body: message });
    }
  }

  getBatteryInfo() {
    if (!this.battery) return null;

    return {
      level: Math.round(this.battery.level * 100),
      charging: this.battery.charging,
      chargingTime: this.battery.chargingTime,
      dischargingTime: this.battery.dischargingTime,
      powerSavingMode: this.powerSavingMode
    };
  }
}

// Utilisation
const batteryManager = new BatteryManager();

Fluent API (Method Chaining)

Le pattern Fluent API, aussi connu sous le nom de Method Chaining, est un design pattern qui permet de creer des interfaces de programmation elegantes ou les appels de methodes peuvent etre enchaines. Ce pattern ameliore la lisibilite du code et rend l’API plus intuitive a utiliser.

Principe fondamental

Le secret du Method Chaining est simple : chaque methode retourne this (l’instance courante de l’objet), permettant d’appeler immediatement une autre methode sur le resultat.

// Sans Fluent API
const builder = new StringBuilder();
builder.append("Hello");
builder.append(" ");
builder.append("World");
const result = builder.toString();

// Avec Fluent API
const result = new StringBuilder()
  .append("Hello")
  .append(" ")
  .append("World")
  .toString();

Exemple historique : jQuery

jQuery est l’exemple le plus celebre d’implementation du pattern Fluent API en JavaScript :

// Le chainage jQuery classique
$('#element')
  .addClass('active')
  .css('color', 'red')
  .fadeIn(300)
  .on('click', handleClick)
  .find('.child')
  .hide();

Cette syntaxe elegante a revolutionne le developpement front-end et reste une reference en matiere de design d’API.

Implementation avec les classes ES6

Voici comment implementer le pattern Fluent API avec les classes modernes :

class Item {
  constructor(text, type) {
    this.text = text;
    this.emphasis = false;
    this.strong = false;
    this.type = type;
    this.classes = [];
    this.attributes = {};
  }

  addClass(className) {
    this.classes.push(className);
    return this; // Retourne this pour le chainage
  }

  setAttribute(name, value) {
    this.attributes[name] = value;
    return this;
  }

  makeEmphasis() {
    this.emphasis = true;
    return this;
  }

  makeStrong() {
    this.strong = true;
    return this;
  }

  toHtml() {
    let content = this.text;

    if (this.emphasis) content = `<em>${content}</em>`;
    if (this.strong) content = `<strong>${content}</strong>`;

    const classAttr = this.classes.length > 0
      ? ` class="${this.classes.join(' ')}"`
      : '';

    const otherAttrs = Object.entries(this.attributes)
      .map(([key, val]) => ` ${key}="${val}"`)
      .join('');

    return `<${this.type}${classAttr}${otherAttrs}>${content}</${this.type}>`;
  }
}

// Utilisation
const paragraph = new Item('Texte important', 'p')
  .addClass('highlight')
  .addClass('intro')
  .makeStrong()
  .setAttribute('data-id', '123');

console.log(paragraph.toHtml());
// <p class="highlight intro" data-id="123"><strong>Texte important</strong></p>

Builder Pattern complet : QueryBuilder

Voici un exemple plus avance d’un QueryBuilder SQL utilisant le pattern Fluent API :

class QueryBuilder {
  constructor() {
    this.queryType = 'SELECT';
    this.selectColumns = ['*'];
    this.tableName = '';
    this.whereConditions = [];
    this.orderByColumns = [];
    this.limitValue = null;
    this.offsetValue = null;
    this.joinClauses = [];
    this.groupByColumns = [];
  }

  static create() {
    return new QueryBuilder();
  }

  select(...columns) {
    this.selectColumns = columns.length > 0 ? columns : ['*'];
    return this;
  }

  from(table) {
    this.tableName = table;
    return this;
  }

  where(column, operator, value) {
    this.whereConditions.push({
      column,
      operator,
      value,
      connector: 'AND'
    });
    return this;
  }

  orWhere(column, operator, value) {
    this.whereConditions.push({
      column,
      operator,
      value,
      connector: 'OR'
    });
    return this;
  }

  whereIn(column, values) {
    this.whereConditions.push({
      column,
      operator: 'IN',
      value: values,
      connector: 'AND'
    });
    return this;
  }

  whereNull(column) {
    this.whereConditions.push({
      column,
      operator: 'IS NULL',
      value: null,
      connector: 'AND'
    });
    return this;
  }

  join(table, column1, operator, column2) {
    this.joinClauses.push({
      type: 'INNER',
      table,
      column1,
      operator,
      column2
    });
    return this;
  }

  leftJoin(table, column1, operator, column2) {
    this.joinClauses.push({
      type: 'LEFT',
      table,
      column1,
      operator,
      column2
    });
    return this;
  }

  orderBy(column, direction = 'ASC') {
    this.orderByColumns.push({ column, direction: direction.toUpperCase() });
    return this;
  }

  groupBy(...columns) {
    this.groupByColumns = columns;
    return this;
  }

  limit(value) {
    this.limitValue = value;
    return this;
  }

  offset(value) {
    this.offsetValue = value;
    return this;
  }

  buildWhereClause() {
    if (this.whereConditions.length === 0) return '';

    return ' WHERE ' + this.whereConditions.map((cond, index) => {
      const connector = index === 0 ? '' : ` ${cond.connector} `;

      if (cond.operator === 'IN') {
        const values = cond.value.map(v =>
          typeof v === 'string' ? `'${v}'` : v
        ).join(', ');
        return `${connector}${cond.column} IN (${values})`;
      }

      if (cond.operator === 'IS NULL') {
        return `${connector}${cond.column} IS NULL`;
      }

      const value = typeof cond.value === 'string'
        ? `'${cond.value}'`
        : cond.value;

      return `${connector}${cond.column} ${cond.operator} ${value}`;
    }).join('');
  }

  build() {
    let query = `${this.queryType} ${this.selectColumns.join(', ')}`;
    query += ` FROM ${this.tableName}`;

    // Joins
    this.joinClauses.forEach(join => {
      query += ` ${join.type} JOIN ${join.table} ON ${join.column1} ${join.operator} ${join.column2}`;
    });

    // Where
    query += this.buildWhereClause();

    // Group By
    if (this.groupByColumns.length > 0) {
      query += ` GROUP BY ${this.groupByColumns.join(', ')}`;
    }

    // Order By
    if (this.orderByColumns.length > 0) {
      const orderStr = this.orderByColumns
        .map(o => `${o.column} ${o.direction}`)
        .join(', ');
      query += ` ORDER BY ${orderStr}`;
    }

    // Limit & Offset
    if (this.limitValue !== null) {
      query += ` LIMIT ${this.limitValue}`;
    }
    if (this.offsetValue !== null) {
      query += ` OFFSET ${this.offsetValue}`;
    }

    return query + ';';
  }

  // Methode pour reinitialiser le builder
  reset() {
    return new QueryBuilder();
  }
}

// Utilisation
const query = QueryBuilder.create()
  .select('users.id', 'users.name', 'orders.total')
  .from('users')
  .leftJoin('orders', 'users.id', '=', 'orders.user_id')
  .where('users.status', '=', 'active')
  .whereIn('users.role', ['admin', 'editor'])
  .orderBy('users.created_at', 'DESC')
  .limit(10)
  .offset(0)
  .build();

console.log(query);
// SELECT users.id, users.name, orders.total FROM users
// LEFT JOIN orders ON users.id = orders.user_id
// WHERE users.status = 'active' AND users.role IN ('admin', 'editor')
// ORDER BY users.created_at DESC LIMIT 10 OFFSET 0;

Classes Section et Article enrichies

class Section {
  constructor(header) {
    this.header = header;
    this.paragraphs = [];
    this.level = 2;
  }

  setLevel(level) {
    this.level = level;
    return this;
  }

  addParagraph(text) {
    const paragraph = new Item(text, 'p');
    this.paragraphs.push(paragraph);
    return paragraph; // Retourne l'item pour configuration supplementaire
  }

  toHtml() {
    const headerTag = `h${this.level}`;
    const paragraphsHtml = this.paragraphs.map(p => p.toHtml()).join('\n');
    return `<section>
  <${headerTag}>${this.header}</${headerTag}>
  ${paragraphsHtml}
</section>`;
  }
}

class List {
  constructor(ordered = false) {
    this.ordered = ordered;
    this.items = [];
  }

  addItem(text) {
    const item = new Item(text, 'li');
    this.items.push(item);
    return this; // Permet le chainage
  }

  toHtml() {
    const tag = this.ordered ? 'ol' : 'ul';
    const itemsHtml = this.items.map(item => item.toHtml()).join('\n');
    return `<${tag}>
${itemsHtml}
</${tag}>`;
  }
}

class Article {
  constructor(topic) {
    this.topic = topic;
    this.sections = [];
    this.lists = [];
    this.currentSection = null;
    this.currentList = null;
    this.metadata = {};
  }

  static create(topic) {
    return new Article(topic);
  }

  setMetadata(key, value) {
    this.metadata[key] = value;
    return this;
  }

  section(text) {
    const section = new Section(text);
    this.sections.push(section);
    this.currentSection = section;
    return this;
  }

  list(ordered = false) {
    const list = new List(ordered);
    this.lists.push(list);
    this.currentList = list;
    return this;
  }

  addParagraph(text) {
    if (!this.currentSection) {
      throw new Error('Aucune section active. Appelez section() d\'abord.');
    }
    this.currentSection.addParagraph(text);
    return this;
  }

  addListItem(text) {
    if (!this.currentList) {
      throw new Error('Aucune liste active. Appelez list() d\'abord.');
    }
    this.currentList.addItem(text);
    return this;
  }

  toHtml() {
    const sectionsHtml = this.sections.map(s => s.toHtml()).join('\n');
    const listsHtml = this.lists.map(l => l.toHtml()).join('\n');

    return `<article>
  <h1>${this.topic}</h1>
  ${sectionsHtml}
  ${listsHtml}
</article>`;
  }
}

// Utilisation fluide
const article = Article.create('Les APIs Web Modernes')
  .setMetadata('author', 'Mahmoud DEVO')
  .setMetadata('date', '2025-01-15')
  .section('Introduction')
  .addParagraph('Les APIs web permettent des interactions riches.')
  .addParagraph('Nous allons explorer plusieurs exemples.')
  .section('Conclusion')
  .addParagraph('Ces APIs ouvrent de nouvelles possibilites.')
  .list()
  .addListItem('Battery API')
  .addListItem('Web Crypto')
  .addListItem('Fluent Design')
  .toHtml();

console.log(article);

Web Cryptography API

La Web Cryptography API (window.crypto.subtle) fournit des primitives cryptographiques natives dans le navigateur. Cette API permet de realiser des operations de chiffrement, dechiffrement, hachage et signature sans recourir a des bibliotheques externes.

Algorithmes supportes

TypeAlgorithmesUsage
Chiffrement symetriqueAES-CBC, AES-CTR, AES-GCMChiffrement rapide de donnees
Chiffrement asymetriqueRSA-OAEPEchange de cles, chiffrement
SignatureRSA-PSS, ECDSAAuthentification, integrite
HachageSHA-1, SHA-256, SHA-384, SHA-512Empreintes, verification
Derivation de clesPBKDF2, HKDFGeneration de cles depuis mots de passe

Nombres aleatoires cryptographiques

La methode crypto.getRandomValues() genere des nombres aleatoires securises :

// Generer des octets aleatoires
const randomBytes = new Uint8Array(16);
crypto.getRandomValues(randomBytes);
console.log('Octets aleatoires:', randomBytes);

// Generer un UUID v4
function generateUUID() {
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);

  // Set version (4) et variant (8, 9, A, ou B)
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;

  const hex = Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}

console.log('UUID:', generateUUID());
// UUID: 3b12f1df-5232-4bff-9f2a-1a2b3c4d5e6f

// Generer un token securise
function generateSecureToken(length = 32) {
  const bytes = new Uint8Array(length);
  crypto.getRandomValues(bytes);
  return Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

console.log('Token:', generateSecureToken());

Generation de cles symetriques (AES-GCM)

AES-GCM est recommande pour le chiffrement symetrique car il fournit a la fois confidentialite et authenticite :

async function generateAESKey() {
  const key = await crypto.subtle.generateKey(
    {
      name: 'AES-GCM',
      length: 256 // 128, 192, ou 256 bits
    },
    true, // Extractable (peut etre exportee)
    ['encrypt', 'decrypt']
  );

  return key;
}

async function encryptWithAES(key, plaintext) {
  // Generer un IV unique pour chaque chiffrement
  const iv = crypto.getRandomValues(new Uint8Array(12));

  const encodedText = new TextEncoder().encode(plaintext);

  const ciphertext = await crypto.subtle.encrypt(
    {
      name: 'AES-GCM',
      iv: iv
    },
    key,
    encodedText
  );

  // Retourner IV + ciphertext (IV necessaire pour dechiffrement)
  return {
    iv: iv,
    ciphertext: new Uint8Array(ciphertext)
  };
}

async function decryptWithAES(key, iv, ciphertext) {
  const decrypted = await crypto.subtle.decrypt(
    {
      name: 'AES-GCM',
      iv: iv
    },
    key,
    ciphertext
  );

  return new TextDecoder().decode(decrypted);
}

// Exemple d'utilisation
async function demonstrateAES() {
  const key = await generateAESKey();
  const message = 'Donnees sensibles a proteger';

  const encrypted = await encryptWithAES(key, message);
  console.log('Chiffre:', encrypted.ciphertext);

  const decrypted = await decryptWithAES(key, encrypted.iv, encrypted.ciphertext);
  console.log('Dechiffre:', decrypted);
}

demonstrateAES();

Generation de cles asymetriques (RSA-OAEP)

Le chiffrement asymetrique est utile pour l’echange securise de cles :

async function generateRSAKeyPair() {
  const keyPair = await crypto.subtle.generateKey(
    {
      name: 'RSA-OAEP',
      modulusLength: 2048,
      publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
      hash: 'SHA-256'
    },
    true,
    ['encrypt', 'decrypt']
  );

  return keyPair;
}

async function encryptWithRSA(publicKey, plaintext) {
  const encodedText = new TextEncoder().encode(plaintext);

  const encrypted = await crypto.subtle.encrypt(
    { name: 'RSA-OAEP' },
    publicKey,
    encodedText
  );

  return new Uint8Array(encrypted);
}

async function decryptWithRSA(privateKey, ciphertext) {
  const decrypted = await crypto.subtle.decrypt(
    { name: 'RSA-OAEP' },
    privateKey,
    ciphertext
  );

  return new TextDecoder().decode(decrypted);
}

// Exportation de cles (pour stockage/transmission)
async function exportKey(key, type = 'spki') {
  // 'spki' pour cle publique, 'pkcs8' pour cle privee
  const exported = await crypto.subtle.exportKey(type, key);
  return new Uint8Array(exported);
}

// Exemple complet
async function demonstrateRSA() {
  const keyPair = await generateRSAKeyPair();

  const message = 'Message secret';
  const encrypted = await encryptWithRSA(keyPair.publicKey, message);
  const decrypted = await decryptWithRSA(keyPair.privateKey, encrypted);

  console.log('Original:', message);
  console.log('Dechiffre:', decrypted);
}

demonstrateRSA();

Hachage de donnees (SHA-256)

Le hachage est utile pour verifier l’integrite des donnees :

async function hashSHA256(message) {
  const encodedMessage = new TextEncoder().encode(message);

  const hashBuffer = await crypto.subtle.digest('SHA-256', encodedMessage);

  // Convertir en hexadecimal
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  return hashHex;
}

// Fonction utilitaire pour comparer des hashes
async function verifyHash(message, expectedHash) {
  const actualHash = await hashSHA256(message);
  return actualHash === expectedHash;
}

// Hachage de fichier
async function hashFile(file) {
  const arrayBuffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);

  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

// Exemple
async function demonstrateHashing() {
  const message = 'Donnees a verifier';
  const hash = await hashSHA256(message);
  console.log('SHA-256:', hash);
  // SHA-256: 64 caracteres hexadecimaux

  const isValid = await verifyHash(message, hash);
  console.log('Hash valide:', isValid); // true
}

demonstrateHashing();

Signature et verification (ECDSA)

ECDSA permet de signer des donnees et de verifier leur authenticite :

async function generateECDSAKeyPair() {
  const keyPair = await crypto.subtle.generateKey(
    {
      name: 'ECDSA',
      namedCurve: 'P-256' // P-256, P-384, ou P-521
    },
    true,
    ['sign', 'verify']
  );

  return keyPair;
}

async function signData(privateKey, data) {
  const encodedData = new TextEncoder().encode(data);

  const signature = await crypto.subtle.sign(
    {
      name: 'ECDSA',
      hash: 'SHA-256'
    },
    privateKey,
    encodedData
  );

  return new Uint8Array(signature);
}

async function verifySignature(publicKey, signature, data) {
  const encodedData = new TextEncoder().encode(data);

  const isValid = await crypto.subtle.verify(
    {
      name: 'ECDSA',
      hash: 'SHA-256'
    },
    publicKey,
    signature,
    encodedData
  );

  return isValid;
}

// Exemple complet
async function demonstrateSignature() {
  const keyPair = await generateECDSAKeyPair();

  const document = 'Contrat important a signer';
  const signature = await signData(keyPair.privateKey, document);

  console.log('Signature:', signature);

  const isValid = await verifySignature(keyPair.publicKey, signature, document);
  console.log('Signature valide:', isValid); // true

  // Tentative de falsification
  const tamperedDoc = 'Contrat modifie';
  const isTamperedValid = await verifySignature(keyPair.publicKey, signature, tamperedDoc);
  console.log('Document falsifie valide:', isTamperedValid); // false
}

demonstrateSignature();

Cas d’usage pratiques

Stockage securise de donnees sensibles

Voici une classe complete pour stocker des donnees chiffrees dans localStorage :

class SecureStorage {
  constructor() {
    this.keyName = 'secure_storage_key';
  }

  async init(password) {
    // Deriver une cle depuis le mot de passe
    const encoder = new TextEncoder();
    const passwordKey = await crypto.subtle.importKey(
      'raw',
      encoder.encode(password),
      'PBKDF2',
      false,
      ['deriveBits', 'deriveKey']
    );

    // Salt stocke ou genere
    let salt = localStorage.getItem(this.keyName + '_salt');
    if (!salt) {
      const newSalt = crypto.getRandomValues(new Uint8Array(16));
      salt = btoa(String.fromCharCode(...newSalt));
      localStorage.setItem(this.keyName + '_salt', salt);
    }

    const saltArray = new Uint8Array(atob(salt).split('').map(c => c.charCodeAt(0)));

    // Deriver la cle AES
    this.key = await crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: saltArray,
        iterations: 100000,
        hash: 'SHA-256'
      },
      passwordKey,
      { name: 'AES-GCM', length: 256 },
      false,
      ['encrypt', 'decrypt']
    );
  }

  async setItem(key, value) {
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encodedValue = new TextEncoder().encode(JSON.stringify(value));

    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv: iv },
      this.key,
      encodedValue
    );

    const combined = new Uint8Array(iv.length + encrypted.byteLength);
    combined.set(iv);
    combined.set(new Uint8Array(encrypted), iv.length);

    localStorage.setItem(key, btoa(String.fromCharCode(...combined)));
  }

  async getItem(key) {
    const stored = localStorage.getItem(key);
    if (!stored) return null;

    const combined = new Uint8Array(atob(stored).split('').map(c => c.charCodeAt(0)));
    const iv = combined.slice(0, 12);
    const ciphertext = combined.slice(12);

    try {
      const decrypted = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: iv },
        this.key,
        ciphertext
      );

      return JSON.parse(new TextDecoder().decode(decrypted));
    } catch (e) {
      console.error('Echec du dechiffrement:', e);
      return null;
    }
  }

  removeItem(key) {
    localStorage.removeItem(key);
  }
}

// Utilisation
async function demonstrateSecureStorage() {
  const storage = new SecureStorage();
  await storage.init('MonMotDePasseSecret123');

  await storage.setItem('userData', {
    email: 'user@example.com',
    apiKey: 'sk_live_xxx'
  });

  const data = await storage.getItem('userData');
  console.log('Donnees recuperees:', data);
}

demonstrateSecureStorage();

Introduction a WebAuthn (authentification sans mot de passe)

WebAuthn utilise la cryptographie pour une authentification forte :

// Exemple simplifie d'enregistrement WebAuthn
async function registerWebAuthn(username) {
  // Challenge genere par le serveur
  const challenge = crypto.getRandomValues(new Uint8Array(32));

  const publicKeyCredentialCreationOptions = {
    challenge: challenge,
    rp: {
      name: "Mon Application",
      id: window.location.hostname
    },
    user: {
      id: new TextEncoder().encode(username),
      name: username,
      displayName: username
    },
    pubKeyCredParams: [
      { alg: -7, type: "public-key" },  // ES256
      { alg: -257, type: "public-key" } // RS256
    ],
    authenticatorSelection: {
      authenticatorAttachment: "platform",
      userVerification: "required"
    },
    timeout: 60000
  };

  try {
    const credential = await navigator.credentials.create({
      publicKey: publicKeyCredentialCreationOptions
    });

    console.log('Credential cree:', credential);
    // Envoyer credential.response au serveur pour verification

    return credential;
  } catch (error) {
    console.error('Erreur WebAuthn:', error);
    throw error;
  }
}

Bonnes Pratiques

Securite avec Web Crypto

  1. Ne jamais stocker les cles privees en clair : Utilisez extractable: false quand possible, ou chiffrez les cles exportees.

  2. Utilisez des IV/nonces uniques : Chaque operation de chiffrement doit utiliser un IV unique. Ne reutilisez jamais un IV avec la meme cle.

  3. Preferez AES-GCM a AES-CBC : GCM fournit l’authentification en plus du chiffrement, protegeant contre les attaques par manipulation.

  4. Utilisez des algorithmes modernes : Preferez ECDSA a RSA pour les signatures (plus rapide, cles plus courtes), et SHA-256 minimum pour le hachage.

  5. Validez toujours les entrees : Avant de dechiffrer ou verifier, validez le format et la taille des donnees.

Compatibilite et performance

  1. Verifiez la disponibilite : Testez window.crypto et window.crypto.subtle avant utilisation.

  2. Gerez les erreurs : Les operations crypto peuvent echouer (cle invalide, donnees corrompues). Utilisez try/catch.

  3. Evitez les operations bloquantes : Les operations crypto sont asynchrones. Utilisez async/await correctement.

  4. Limitez la taille des donnees RSA : RSA ne peut chiffrer que des donnees limitees. Pour des donnees volumineuses, utilisez le chiffrement hybride (RSA pour la cle AES, AES pour les donnees).

Fluent API

  1. Documentez le chainage : Indiquez clairement quelles methodes peuvent etre chainees et lesquelles terminent la chaine.

  2. Retournez toujours this : Sauf pour les methodes terminales comme build() ou toString().

  3. Validez l’etat : Verifiez que les preconditions sont remplies avant d’executer une methode.


Pieges Courants

Battery API

  • Piege : Supposer que l’API est toujours disponible

  • Solution : Toujours verifier 'getBattery' in navigator avant utilisation

  • Piege : Ignorer les valeurs Infinity pour chargingTime/dischargingTime

  • Solution : Gerer explicitement le cas Infinity dans votre logique

if (battery.dischargingTime === Infinity) {
  console.log('Temps de decharge inconnu ou en charge');
}

Fluent API

  • Piege : Oublier de retourner this dans une methode

  • Solution : Verifier systematiquement le return this a la fin de chaque methode chainable

  • Piege : Methodes qui modifient l’etat de maniere inattendue

  • Solution : Documenter les effets de bord et considerer l’immutabilite

Web Crypto

  • Piege : Reutiliser le meme IV pour plusieurs chiffrements

  • Solution : Generer un nouveau IV pour chaque operation avec crypto.getRandomValues()

  • Piege : Stocker des cles en clair dans localStorage

  • Solution : Utilisez extractable: false ou chiffrez les cles avant stockage

  • Piege : Oublier que les operations sont asynchrones

  • Solution : Toujours utiliser await ou .then() avec les methodes de crypto.subtle

  • Piege : Ne pas gerer les erreurs de dechiffrement

  • Solution : Enveloppez les operations dans try/catch et gerez gracieusement les echecs

try {
  const decrypted = await crypto.subtle.decrypt(/*...*/);
} catch (error) {
  if (error.name === 'OperationError') {
    console.error('Dechiffrement echoue - donnees corrompues ou mauvaise cle');
  }
}

Conclusion

Les APIs web que nous avons explorees representent differentes facettes du developpement moderne :

APIUsage principalNiveau de support
Battery StatusAdapter l’UX selon l’autonomieLimite (Chrome/Edge)
Fluent APIArchitecture de code eleganteDesign pattern universel
Web CryptoSecurite cote clientExcellent (tous navigateurs modernes)

La Battery Status API, bien que deprecee dans certains navigateurs, reste utile pour les PWA ciblant Chrome. Elle permet d’offrir une experience utilisateur adaptative en fonction de l’etat de la batterie.

Le pattern Fluent API transcende les APIs specifiques : c’est un principe de conception qui rend votre code plus lisible et maintenable. Que vous construisiez un QueryBuilder, un generateur HTML ou toute autre DSL, ce pattern est inestimable.

La Web Cryptography API est essentielle pour toute application manipulant des donnees sensibles. Elle offre des primitives cryptographiques robustes directement dans le navigateur, eliminant le besoin de bibliotheques externes pour les operations de chiffrement courantes.

En combinant ces connaissances, vous pouvez creer des applications web qui sont a la fois elegantes dans leur conception, adaptatives aux conditions de l’utilisateur, et securisees dans leur traitement des donnees.

Points cles a retenir

  • Verifiez toujours la disponibilite des APIs avant utilisation
  • Le pattern Fluent API ameliore la lisibilite via le retour de this
  • Web Crypto fournit des primitives securisees sans dependances externes
  • Utilisez AES-GCM pour le chiffrement symetrique avec authentification
  • Generez toujours des IV/nonces uniques pour chaque chiffrement
  • Gerez les erreurs de maniere appropriee pour toutes les operations crypto

Ressources supplementaires

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles