Swift

Pachetul HonkMe este un client Swift 6.2 cu concurență strictă, async/await și fără dependențe. Rulează pe macOS, iOS și Linux, pentru Swift pe server, unelte și CI.

Pachet
HonkMe Publicat
Cod sursă
github.com/honk-me/honk-swift · Licență MIT
Cerințe
Swift 6.2; macOS 14+, iOS 17+ sau Linux.

Instalare

În Xcode: File ▸ Add Package Dependencies, apoi lipește 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"),

Oricine are o cheie API (honk_…) poate trimite mesaje în proiectul ei. Ține-o pe servere, în joburi și în secretele din CI, niciodată într-o aplicație de browser, de mobil sau de desktop.

Trimite un eveniment

Nu pune niciodată o cheie API într-o aplicație iOS sau macOS: tot ce livrezi într-o aplicație poate fi extras. O aplicație care trebuie să te anunțe apelează propriul tău backend, iar acesta apelează 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")

Rețetă: cererea unui client, cu Vapor

Message este Sendable, așa că îl poți construi în handler și îl poți trimite în afara fluxului cererii. Creează un singur Honk la pornire și folosește-l peste tot: are propriul URLSession cu 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
    }
}

Scala Honk și incidentele

Helperele primesc o closure care editează mesajul.

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

Erori

Cazurile HonkError au un Failure cu statusul, codul, fiecare câmp invalid și retryAfter; isRetryable îți spune ce să faci.

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)
}

Reîncercări care nu trimit nimic de două ori

  • Fiecare trimitere are un Idempotency-Key: al tău sau un UUIDv7 nou. Aceeași cheie e refolosită la fiecare reîncercare, iar timp de 24 de ore Honk răspunde la o repetare cu id-ul original și duplicate: true, așa că un răspuns pierdut nu creează niciodată un al doilea mesaj.
  • Se reîncearcă doar erorile de rețea, cererile care expiră, 429 și 5xx, cu backoff exponențial și jitter complet, niciodată mai devreme decât Retry-After de la server. Celelalte răspunsuri 4xx nu se reîncearcă niciodată: corectează cererea.
  • Fiecare încercare expiră după 5 secunde, iar totul se oprește după 30. Dacă ar trebui să aștepte mai mult, de exemplu până se resetează cota zilnică la miezul nopții, eșuează imediat și îți spune când să reîncerci.
  • Câmpurile sunt verificate înainte de trimitere, iar toate câmpurile invalide sunt raportate deodată.