Node.js

Le paquet honk-me est un client TypeScript sans dépendance d’exécution, pour Node 18 et plus récent, Bun et Deno. Utilisez-le dans du code back-end, des fonctions et des scripts.

Paquet
honk-me Publié
Code source
github.com/honk-me/honk-node · Licence MIT
Prérequis
Node 18 ou plus récent (fetch global), Bun ou Deno.

Installation

Créez ensuite un projet et une clé d’ingestion dans l’app web Honk.

Terminal
npm install honk-me

Une clé d’ingestion (honk_…) permet à quiconque la détient d’envoyer des messages dans son projet. Gardez-la sur vos serveurs, dans vos tâches et dans les secrets de votre CI, jamais dans un navigateur ni dans une app mobile ou de bureau.

Envoyer un événement

Honk.fromEnv() lit HONK_URL, HONK_KEY et les valeurs par défaut facultatives HONK_SOURCE, HONK_ENVIRONMENT et HONK_CHANNEL. En CommonJS, require('honk-me') fonctionne aussi.

import { Honk } from 'honk-me';

const honk = new Honk({ url: process.env.HONK_URL, key: process.env.HONK_KEY });
await honk.beep('Backup finished', 'nightly pg_dump took 42 s');

Recette : une demande client sur votre téléphone

Un groupe par demande et une clé d’idempotence stable : deux clients ne se retrouvent jamais dans la même notification, et un webhook relancé ne fait jamais vibrer deux fois. La promesse n’est pas attendue : un réseau lent ne ralentit jamais la requête du client.

import { Honk } from 'honk-me';

const honk = Honk.fromEnv(); // create once, reuse (keep-alive)

export async function onCustomerRequest(req) {
  // ...save the request first, then notify without blocking the response:
  honk
    .send(
      {
        title: `New request: ${req.subject}`.slice(0, 160),
        message: `${req.name} (${req.company}) asked: ${req.body}`.slice(0, 2000),
        priority: 'high', // push right away
        category: 'customers',
        channel: 'requests',
        groupKey: `requests/${req.id}`, // one group per request
        url: `https://shop.example.com/admin/requests/${req.id}`, // https only, shown as "Open link"
        metadata: { request_id: String(req.id) },
      },
      { idempotencyKey: `request-${req.id}` }, // same request, same key: never a duplicate
    )
    .catch((err) => console.warn('honk:', err.message)); // never fail the customer's request
}

L’échelle Honk et les incidents

Un helper par niveau. problem et recovery nécessitent une clé de groupe ; le rétablissement clôt l’épisode ouvert par le problème.

await honk.loud('Disk 91%', '/var on app-01', { groupKey: 'disk/app-01/var' });
await honk.light('Deploy started', 'v4.2.0 to production', { channel: 'deploys' });
await honk.problem('db/backup', 'Backup failed', 'pg_dump exited with 1');      // a long honk by default
await honk.recovery('db/backup', 'Backup OK', 'pg_dump finished in 41 s');      // a beep by default

Erreurs

Chaque erreur est une HonkError avec un kind et retryable. Les erreurs de validation sont des bugs ; les erreurs temporaires peuvent retourner dans votre file d’attente avec la même clé.

import { HonkError, HonkValidationError } from 'honk-me';

try {
  await honk.send(msg, { idempotencyKey: `order-${order.id}-failed` });
} catch (err) {
  if (err instanceof HonkValidationError) logger.error(err.fields);   // a bug: don't retry
  else if (err instanceof HonkError && err.retryable) queue.retryLater(err.idempotencyKey, err.retryAfter);
  else throw err;
}

Bun et Deno

Bun : bun add honk-me, puis utilisez-le comme dans Node. Deno l’importe depuis npm :

import { Honk } from 'npm:honk-me'; // Deno
const honk = new Honk({ url: Deno.env.get('HONK_URL')!, key: Deno.env.get('HONK_KEY')! });

Des nouvelles tentatives sans doublon

  • Chaque envoi porte une Idempotency-Key : la vôtre, ou un UUIDv7 généré pour l’occasion. La même clé est réutilisée à chaque nouvelle tentative, et pendant 24 heures Honk répond à une requête rejouée avec l’identifiant d’origine et duplicate: true : une réponse perdue ne crée jamais de second message.
  • Une nouvelle tentative n’a lieu qu’en cas d’erreur réseau, de délai dépassé, de 429 ou de 5xx, avec un backoff exponentiel et un jitter complet, jamais avant le Retry-After du serveur. Les autres réponses 4xx ne donnent lieu à aucune nouvelle tentative : corrigez plutôt la requête.
  • Chaque tentative expire au bout de 5 secondes, et tout s’arrête au bout de 30 secondes au total. Une attente qui dépasserait cette échéance, comme un quota quotidien remis à zéro à minuit, échoue immédiatement et indique quand réessayer.
  • Les champs sont vérifiés localement avant l’envoi, et tous les champs invalides sont signalés en une seule fois.