Swift

El paquete HonkMe es un cliente en Swift 6.2 con concurrencia estricta, async/await y sin dependencias. Funciona en macOS, iOS y Linux, para Swift en el servidor, herramientas y CI.

Paquete
HonkMe Publicado
Código fuente
github.com/honk-me/honk-swift · Licencia MIT
Requisitos
Swift 6.2; macOS 14+, iOS 17+ o Linux.

Instalación

En Xcode: File ▸ Add Package Dependencies, y pega https://github.com/honk-me/honk-swift.

Package.swift
// Package.swift
.package(url: "https://github.com/honk-me/honk-swift.git", from: "0.1.0"),
// target:
.product(name: "HonkMe", package: "honk-swift"),

Quien tenga una clave de ingesta (honk_…) puede enviar mensajes a su proyecto. Guárdala en servidores, tareas y secretos de CI, nunca en un navegador ni en una app móvil o de escritorio.

Enviar un evento

Nunca incluyas una clave de ingesta en una app de iOS o macOS: todo lo que va dentro de una app se puede extraer. Si una app necesita avisarte, que llame a tu propio backend y que este llame a Honk.

import HonkMe

let honk = try Honk.fromEnvironment()          // HONK_URL, HONK_KEY (+ HONK_SOURCE, HONK_ENVIRONMENT, HONK_CHANNEL)
try await honk.beep("Backup finished", "nightly pg_dump took 42 s")

Receta: una solicitud de cliente con Vapor

Message es Sendable, así que puedes crearlo en el handler y enviarlo sin bloquear la respuesta. Crea un solo Honk al arrancar y compártelo: tiene su propia URLSession con keep-alive.

import HonkMe
import Vapor

func routes(_ app: Application, honk: Honk) {
    app.post("quote") { req async throws -> HTTPStatus in
        let quote = try req.content.decode(QuoteRequest.self)
        try await quote.save(on: req.db)                               // store it first
        let id = try quote.requireID()
        // Message is Sendable: build it here, send it off the request path.
        let message = Message(
            "\(quote.name) (\(quote.company)) asked: \(quote.body.prefix(2000))",
            title: "New request: \(quote.subject)".prefix(150).description,
            severity: .light,
            priority: .high,                                           // push right away
            category: .customers,
            channel: "requests",
            groupKey: "requests/\(id)",                                // one group per request
            url: "https://shop.example.com/admin/requests/\(id)",      // https only
            metadata: ["request_id": .string("\(id)")]
        )
        let logger = req.logger
        Task {
            do { try await honk.send(message, idempotencyKey: "request-\(id)") }   // same request, same key
            catch { logger.warning("honk: \(error)") }
        }
        return .accepted
    }
}

La escala Honk e incidentes

Los helpers aceptan un closure que edita el mensaje.

try await honk.loud("Disk 91%", "/var on app-01") { $0.groupKey = "disk/app-01/var" }
try await honk.problem(groupKey: "db/backup", "Backup failed", "pg_dump exited with 1")   // a long honk by default
try await honk.recovery(groupKey: "db/backup", "Backup OK", "pg_dump finished in 41 s")  // a beep by default

Errores

Los casos de HonkError llevan un Failure con el estado, el código, cada campo no válido y retryAfter; isRetryable te dice qué hacer.

do {
    try await honk.long("Payment failed", "Stripe declined order 1042") { $0.groupKey = "payments/stripe" }
} catch HonkError.validation(let failure) {
    logger.error("bug: \(failure.fields)")
} catch let error as HonkError where error.isRetryable {
    queue.retryLater(key: error.failure?.idempotencyKey)
}

Reintentos que nunca envían dos veces

  • Cada envío lleva una Idempotency-Key: la tuya o un UUIDv7 nuevo. La misma clave se reutiliza en cada reintento y, durante 24 horas, Honk responde a una repetición con el id original y duplicate: true, así que una respuesta perdida nunca crea un segundo mensaje.
  • Solo se reintentan los errores de red, los tiempos de espera agotados, 429 y 5xx, con backoff exponencial y jitter completo, y nunca antes del Retry-After del servidor. Las demás respuestas 4xx no se reintentan nunca: hay que corregir la solicitud.
  • Cada intento se corta a los 5 segundos y todo se detiene a los 30. Una espera que superaría ese plazo, como una cuota diaria que se reinicia a medianoche, falla al momento y te dice cuándo reintentar.
  • Los campos se validan antes de enviar, y todos los errores de validación se devuelven juntos.