Table of Contents
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 :
-
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.
-
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.
-
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.
| Navigateur | Support | Notes |
|---|---|---|
| Chrome | Oui | Supporte depuis la version 38 |
| Edge | Oui | Supporte depuis la version 79 |
| Firefox | Non | Retire depuis la version 52 (confidentialite) |
| Safari | Non | Jamais supporte |
| Opera | Oui | Supporte 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
| Type | Algorithmes | Usage |
|---|---|---|
| Chiffrement symetrique | AES-CBC, AES-CTR, AES-GCM | Chiffrement rapide de donnees |
| Chiffrement asymetrique | RSA-OAEP | Echange de cles, chiffrement |
| Signature | RSA-PSS, ECDSA | Authentification, integrite |
| Hachage | SHA-1, SHA-256, SHA-384, SHA-512 | Empreintes, verification |
| Derivation de cles | PBKDF2, HKDF | Generation 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
-
Ne jamais stocker les cles privees en clair : Utilisez
extractable: falsequand possible, ou chiffrez les cles exportees. -
Utilisez des IV/nonces uniques : Chaque operation de chiffrement doit utiliser un IV unique. Ne reutilisez jamais un IV avec la meme cle.
-
Preferez AES-GCM a AES-CBC : GCM fournit l’authentification en plus du chiffrement, protegeant contre les attaques par manipulation.
-
Utilisez des algorithmes modernes : Preferez ECDSA a RSA pour les signatures (plus rapide, cles plus courtes), et SHA-256 minimum pour le hachage.
-
Validez toujours les entrees : Avant de dechiffrer ou verifier, validez le format et la taille des donnees.
Compatibilite et performance
-
Verifiez la disponibilite : Testez
window.cryptoetwindow.crypto.subtleavant utilisation. -
Gerez les erreurs : Les operations crypto peuvent echouer (cle invalide, donnees corrompues). Utilisez try/catch.
-
Evitez les operations bloquantes : Les operations crypto sont asynchrones. Utilisez
async/awaitcorrectement. -
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
-
Documentez le chainage : Indiquez clairement quelles methodes peuvent etre chainees et lesquelles terminent la chaine.
-
Retournez toujours
this: Sauf pour les methodes terminales commebuild()outoString(). -
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 navigatoravant utilisation -
Piege : Ignorer les valeurs
InfinitypourchargingTime/dischargingTime -
Solution : Gerer explicitement le cas
Infinitydans votre logique
if (battery.dischargingTime === Infinity) {
console.log('Temps de decharge inconnu ou en charge');
}
Fluent API
-
Piege : Oublier de retourner
thisdans une methode -
Solution : Verifier systematiquement le
return thisa 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: falseou chiffrez les cles avant stockage -
Piege : Oublier que les operations sont asynchrones
-
Solution : Toujours utiliser
awaitou.then()avec les methodes decrypto.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 :
| API | Usage principal | Niveau de support |
|---|---|---|
| Battery Status | Adapter l’UX selon l’autonomie | Limite (Chrome/Edge) |
| Fluent API | Architecture de code elegante | Design pattern universel |
| Web Crypto | Securite cote client | Excellent (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
In-Article Ad
Dev Mode
Tags
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
Communication cross-origin avec postMessage et WeakMap en JavaScript
Apprenez a utiliser postMessage pour communiquer entre fenetres et WeakMap pour stocker des donnees avec cles faibles en JavaScript.
Guide complet de manipulation des tableaux en JavaScript
Creez et manipulez des tableaux JavaScript : splice, filter, map, join, entries et plus. Toutes les methodes essentielles avec exemples pratiques.
Manipulation des dates en JavaScript : UTC, conversion et formatage
Guide complet sur les dates JavaScript : conversion en chaine, creation de dates UTC, methodes setUTC et bonnes pratiques pour eviter les problemes de fuseaux.