Skip to content

Intégration du SDK

Intégration du SDK Node.js

Ajoutez la gestion de licences Keyright à une application Node.js : installez le paquet keyright, vérifiez les licences hors ligne, activez en ligne, verrouillez les fonctionnalités sur les droits, et gérez les essais et les machines isolées (air-gapped) — en échouant en mode fermé par conception.

Voici le pendant Node.js du parcours du SDK .NET — mêmes formats de licence et de lease, même clé publique, mêmes garanties d’échec en mode fermé, mais pour un runtime Node. Le paquet keyright vérifie les licences hors ligne avec votre clé publique embarquée et, quand vous voulez l’application des sièges et la révocation, s’active en ligne pour mettre en cache une lease signée de courte durée. Chaque bloc de code ci-dessous utilise la vraie surface du SDK ; copiez-les tels quels.

Comme tous les SDK Keyright vérifient les mêmes formats réseau, une gamme de produits multi-langages peut partager un seul tenant Keyright et une seule clé publique. Si votre produit embarque aussi un composant .NET, Python ou Java, ils peuvent tous valider exactement les mêmes clés et les mêmes leases.

Le modèle, en un paragraphe

Vous émettez des clés de licence côté serveur dans Keyright — chaque tenant les signe avec une clé privée RSA qui ne quitte jamais le serveur. Votre application Node n’embarque que la clé publique correspondante et vérifie les licences hors ligne ; une vérification de licence n’a donc besoin d’aucun réseau. Pour l’application des sièges et la révocation, l’application s’active aussi en ligne : elle échange une clé de licence contre une lease signée de courte durée, liée à la machine, puis continue de fonctionner hors ligne jusqu’à l’expiration de cette lease. Tout ce qui ne passe pas la vérification — mauvaise signature, mauvais produit, expiration, révocation, horloge reculée — se résout en édition Free plutôt que de lever une exception. Le SDK échoue en mode fermé.

Configuration éditeur (à faire une seule fois)

Les étapes du tableau de bord sont indépendantes du langage et identiques au guide .NET, cette page les garde donc courtes :

  1. Créez votre produit et notez son slug (p. ex. acme-app) — cette chaîne est ce que votre client envoie en tant que product.
  2. Copiez votre clé publique — la SubjectPublicKeyInfo en base64 depuis l’onglet Integration du tableau de bord. Elle est livrée dans votre application et n’est pas un secret ; elle ne peut que vérifier des signatures, jamais en produire.
  3. Définissez vos niveaux et vos droits — chaque niveau porte un nombre de sièges et les indicateurs/limites nommés qui sont intégrés dans chaque licence de ce niveau.
  4. Émettez des clés — depuis le tableau de bord, votre webhook de facturation ou l’API d’administration.

Pour la version complète de ces étapes (captures d’écran du tableau de bord, appels curl, le modèle niveau/droits, et comment les clés parviennent aux clients), voir Premiers pas et les étapes 1 à 4 et 8 du parcours du SDK .NET. Le reste de cette page n’est que du code client Node.

Installez le SDK

keyright est sur npm (>= 1.1.4). C’est un paquet CommonJS avec zéro dépendance — la vérification des signatures s’appuie sur le module crypto intégré de Node.

npm install keyright
const { KeyrightClient } = require('keyright');

Initialisez le client

Construisez un seul client au démarrage avec KeyrightClient.initialize. Les options sont un objet simple avec des clés en camelCase — il n’y a pas de classe d’options à instancier avec new :

const { KeyrightClient } = require('keyright');

const license = KeyrightClient.initialize({
  product:         'acme-app',                          // doit correspondre au slug du produit pour lequel vous émettez des clés
  publicKeyBase64: 'MIIBIjANBgkq...',                   // la clé publique en base64 de la configuration éditeur
  serviceUrl:      'https://keyright.delta1labs.com',   // omettez-le si vous ne livrez que des fichiers de licence hors ligne

  // Facultatif : garder une ancienne clé valide pendant une fenêtre de rotation
  // additionalPublicKeysBase64: ['<previous public key>'],
});

Seuls product et publicKeyBase64 sont obligatoires ; initialize lève immédiatement une exception si publicKeyBase64 manque, et analyse la ou les clés dès le départ. serviceUrl n’est nécessaire que pour activate/deactivate en ligne.

Au-delà de celles-ci, le client résout une licence à partir de plusieurs sources, par ordre de priorité (la plus haute en premier) :

OptionCe que c’est
licenseStringUne chaîne JSON de licence que vous fournissez directement.
licenseFilePathUn chemin vers un fichier de licence.
envVarNameLe nom d’une variable d’environnement contenant un chemin de fichier de licence. Définissez-la sur 'KEYRIGHT_LICENSE' pour honorer cette convention — elle n’est pas lue tant que vous ne la nommez pas.
configLicensePathUn chemin de fichier de licence fourni par la configuration.
(par défaut)Le chemin app-data du système …/Keyright/<product>/license.json.
(lease)La lease d’activation en cache — écrite pour vous par activate.

En général, vous ne définissez aucune de ces options — l’activation écrit le cache de lease automatiquement. Autres réglages facultatifs : revocationListJson / revocationListPath (livrez une liste de révocation signée pour les builds hors ligne), nodeLockTolerance (par défaut 1), clockTamperToleranceHours (par défaut 24), additionalPublicKeysBase64, et machineComponents (personnalisez ce qui identifie une machine).

Verrouillez les fonctionnalités hors ligne

client.validate() est synchrone. Il résout la meilleure licence parmi les sources ci-dessus, vérifie la signature RSA, la correspondance du produit, la liaison au nœud, l’expiration, la liste de révocation livrée facultative et l’état d’essai/de manipulation de l’horloge — le tout hors ligne — et il ne lève jamais d’exception. Il renvoie { info, source } : déstructurez-le. En cas d’échec, info est une LicenseInfo en édition free portant la raison, et source vous indique quelle source l’a emporté (p. ex. 'lease', 'default', 'none').

const { info, source } = license.validate();

if (info.isPaid) {                 // true pour toute édition au-dessus de Free
  // débloquer les fonctionnalités payantes
}

if (info.edition === 'enterprise') {
  // débloquer les fonctionnalités réservées à Enterprise
}

LicenseInfo expose des champs et des accesseurs en camelCase : status, isValid, isPaid, isTrial, edition, licensee, message, expiryUtc (une Date ou null), daysRemaining (un accesseur, null quand il n’y a pas d’expiration), statusBadge (p. ex. "Enterprise Trial"), et entitlements. status est l’une des chaînes LicenseStatus en minuscules : 'valid', 'no_license', 'malformed', 'signature_invalid', 'expired', 'machine_mismatch', 'revoked', 'clock_tampered'. edition vaut 'free', 'licensed' ou 'enterprise'.

Verrouillez les fonctionnalités individuelles sur les droits plutôt que sur l’édition, pour que modifier le modèle d’un niveau n’oblige pas à livrer du nouveau code. info.entitlements est un EntitlementSet doté de isEnabled(name) et getLimit(name, fallback), et les deux échouent en mode fermé :

const { info } = license.validate();

// Indicateur booléen — absent/non analysable se lit comme désactivé
if (info.entitlements.isEnabled('export')) {
  showExportCommand();
}

// Limite numérique — passez vous-même la valeur de repli en mode fermé ; 'unlimited' se lit comme un nombre énorme
const maxProjects = info.entitlements.getLimit('max-projects', 1);
if (currentProjectCount >= maxProjects) {
  promptToUpgrade();
}

isEnabled considère 'true', '1', 'yes', 'enabled' (sans distinction de casse) comme activé ; toute autre valeur, ou un indicateur absent, est désactivé. getLimit renvoie votre fallback pour une valeur absente ou non entière, et l’entier maximal sûr pour le littéral 'unlimited'. Les noms de droits sont insensibles à la casse.

Le client offre aussi des enveloppes de commodité en un appel qui valident sur le moment — license.isEnabled('export'), license.getLimit('max-projects', 1), license.entitlements() et license.edition() — mais si vous vérifiez plusieurs droits à la fois, appelez validate() une seule fois et réutilisez info.entitlements pour éviter du travail de vérification répété.

Pour savoir pourquoi une vérification a échoué (pour une boîte de dialogue « Enregistrer » ou des diagnostics), lisez info.status et le info.message destiné aux humains.

Activez en ligne avec une clé de licence

Quand un client saisit une clé, appelez await client.activate(key). Il envoie en POST la clé plus un identifiant de machine stable à <serviceUrl>/v1/activate ; le service consomme un siège et renvoie une lease signée de courte durée liée à cette machine. Le SDK vérifie la lease avec votre clé publique embarquée et la met en cache localement, de sorte que chaque validate() ultérieur réussit sans réseau jusqu’à l’expiration de la fenêtre de grâce de la lease.

activate renvoie une LicenseInfo et ne lève pas d’exception pour les chemins d’échec ordinaires (mauvaise clé, limite de sièges, hors ligne, révoquée) — comme validate(), il renvoie une LicenseInfo en mode fermé. Inspectez le résultat :

const info = await license.activate(customerEnteredKey);

if (info.isValid && info.isPaid) {
  // Activée. La lease est en cache ; l'application fonctionne désormais hors ligne jusqu'à son expiration.
  showLicensedUi(info.statusBadge);        // p. ex. "Enterprise" ou "Enterprise Trial"
} else {
  // Affichez info.message ; l'application reste en mode Free.
  showActivationError(info.message);       // "All seats for this license are in use.", etc.
}
  • Les sièges sont appliqués côté serveur. Activer plus de machines que la licence ne l’autorise renvoie un résultat de limite de sièges (message: "All seats for this license are in use.") et aucune lease. Réactiver une machine déjà liée est idempotent — aucun siège supplémentaire n’est consommé.
  • Grâce hors ligne. Si le service est injoignable, activate se replie sur toute lease en cache encore valide et la renvoie, de sorte qu’une brève panne ne bloque pas l’utilisateur. Ce n’est que s’il n’y a aucune lease en cache valide qu’il renvoie une info Free avec l’erreur de connexion dans message.
  • La révocation prend effet au prochain rafraîchissement.

activate ne lève une exception que pour des erreurs de programmation : un serviceUrl manquant ou une clé vide.

L’identifiant de machine qu’il envoie est MachineFingerprint.current().toBoundString() — une empreinte stable par appareil. nodeLockTolerance (par défaut 1) laisse passer une carte réseau ou un disque remplacé sans bloquer l’utilisateur.

Désactivez — libérez un siège

Pour libérer le siège de cette machine (avant de mettre un poste hors service, ou pour que le client puisse déplacer la licence), appelez await client.deactivate(key). Il envoie en POST { key, machineId, product } à <serviceUrl>/v1/free-seat et renvoie la chaîne de statut — 'ok' ou 'not_found'. Sur 'ok', il supprime aussi la lease en cache locale, de sorte que le prochain validate() retombe en Free.

Contrairement à activate, deactivate n’a aucun repli hors ligne : si l’appel HTTP échoue, il lève une exception. Enveloppez-le :

try {
  const status = await license.deactivate(customerEnteredKey);
  if (status === 'ok') {
    // Siège libéré et lease locale effacée.
  } else {
    // 'not_found' — rien n'était lié pour cette clé/machine.
  }
} catch (err) {
  // Impossible de joindre le service d'émission — réessayez une fois de retour en ligne.
}

Machines hors ligne et isolées (air-gapped)

Pour une machine qui ne pourra jamais joindre le service, un opérateur signe une lease hors ligne pour son identifiant de machine (Licenses → offline lease dans le tableau de bord, ou POST /admin/licenses/{id}/offline-lease) et livre le JSON par fichier. L’identifiant de machine à donner à l’opérateur est exactement celui que le SDK dérive :

const { MachineFingerprint } = require('keyright');
console.log(MachineFingerprint.current().toBoundString());

Importez la lease signée avec client.importOfflineLease(json). Il vérifie la lease par le même chemin de code qu’une lease en cache (signature, produit, machine, expiration), la met en cache pour que les appels validate() hors ligne ultérieurs réussissent, et renvoie une LicenseInfo. Contrairement à activate, il lève une exception si la lease est invalide — gérez-la :

const fs = require('fs');

try {
  const info = license.importOfflineLease(fs.readFileSync('acme.lease.json', 'utf8'));
  // info.isValid === true ; la lease est maintenant en cache sur cette machine.
} catch (err) {
  // mauvaise signature / produit / machine, ou expirée
  showActivationError('This offline lease could not be verified: ' + err.message);
}

Il accepte aussi bien une chaîne JSON qu’un objet déjà analysé.

Essais

Une clé d’essai s’active par exactement le même chemin activate qu’une clé payante, et elle est remplacée en douceur quand le client active plus tard une clé payante. Lisez l’état d’essai directement depuis LicenseInfo :

const { info } = license.validate();

if (info.isTrial) {
  const left = info.daysRemaining;                    // p. ex. 27, ou null s'il n'y a pas d'expiration
  showTrialBadge(info.statusBadge, left);             // statusBadge -> "Enterprise Trial"
  // info.expiryUtc est une Date que vous pouvez formater pour une ligne « l'essai se termine le… »
}

La durée d’un essai est comptée à partir de la première activation sur la machine : le SDK persiste un petit fichier d’état local et, une fois la fenêtre d’essai écoulée, validate() renvoie une info Free avec status: 'expired'. Si l’horloge système est reculée en arrière au-delà de clockTamperToleranceHours (24 h par défaut) sur un essai ou un abonnement, validate() renvoie status: 'clock_tampered' ; il se rétablit à l’appel suivant une fois l’heure correcte remise. Les licences perpétuelles ne sont jamais soumises à l’une ou l’autre de ces vérifications. Flux complet : Essais gratuits en libre-service.

Un exemple minimal de bout en bout

const { KeyrightClient } = require('keyright');

const license = KeyrightClient.initialize({
  product:         'acme-app',
  publicKeyBase64: 'MIIBIjANBgkq...',
  serviceUrl:      'https://keyright.delta1labs.com',
});

async function main() {
  // 1. Vérification hors ligne au démarrage — aucun réseau nécessaire.
  let { info } = license.validate();

  // 2. Si non sous licence et que l'utilisateur a collé une clé, activer en ligne.
  if (!info.isPaid && process.env.LICENSE_KEY) {
    info = await license.activate(process.env.LICENSE_KEY);
  }

  // 3. Verrouiller les fonctionnalités sur les droits, en échouant en mode fermé.
  if (info.isPaid && info.entitlements.isEnabled('export')) {
    console.log('Export enabled for', info.licensee, '(' + info.statusBadge + ')');
  } else {
    console.log('Running in Free mode:', info.message || 'no paid license');
  }
}

main();

Dépannage

  • L’activation dit que la clé n’a pas été reconnue. La cause la plus fréquente est une non-correspondance de produit : la clé a été émise pour un produit (ou un tenant) différent du slug product avec lequel votre client est configuré. Le serveur limite l’activation au produit, de sorte qu’une clé valide pour acme-app n’activera pas un client initialisé avec product: 'other-app'. Confirmez que le slug correspond au produit sous lequel vous avez émis la clé.
  • Tout se lit comme Free. C’est voulu — le SDK échoue en mode fermé sur tout problème de vérification plutôt que de lever une exception. Lisez info.status ('no_license', 'signature_invalid', 'expired', 'machine_mismatch', 'revoked', 'clock_tampered') et info.message pour savoir lequel. Un 'signature_invalid' signifie presque toujours que le publicKeyBase64 embarqué ne correspond pas au tenant qui a signé la clé.
  • Une licence à durée limitée refuse soudain de se valider. Si l’horloge système a été reculée en arrière au-delà de clockTamperToleranceHours (24 h par défaut) sur un essai ou un abonnement, le statut est 'clock_tampered'. Ce n’est pas définitif — remettez l’heure correcte et la validation se rétablit à l’appel suivant. Les licences perpétuelles ne sont jamais soumises à cette vérification.
  • deactivate lève une exception. Il n’a aucun repli hors ligne par conception — un appel HTTP qui échoue lève une exception plutôt que de réussir silencieusement. Ce n’est que lorsqu’il renvoie 'ok' que le siège est libéré et la lease locale effacée ; réessayez quand la connectivité revient.
  • importOfflineLease lève une exception. La lease a échoué à la vérification (mauvaise signature/produit/machine, ou expirée) — confirmez que l’opérateur l’a signée pour l’identifiant de machine donné par MachineFingerprint.current().toBoundString() sur ce poste.

Voir aussi