UNPKG

datadog-ux-utils

Version:

Datadog RUM focused UX & performance toolkit: API guards (retry, breaker, rate), React telemetry (error boundary, profiler, Suspense), web vitals & resource observers, offline queues.

146 lines (145 loc) 4.7 kB
import { a as h } from "./datadog-irY4zUa4.js"; const i = { // 0 => use windowMs at runtime reportDebounceMs: 5e3, sampleRate: 100, countOnFailure: !0, overflowStrategy: "block" }; class f { constructor(e) { this.buckets = /* @__PURE__ */ new Map(); const s = e.keyFn ?? u, t = e.blockDurationMs ?? 0; this.cfg = { ...e, keyFn: s, blockDurationMs: t || e.windowMs, reportDebounceMs: e.reportDebounceMs ?? i.reportDebounceMs, sampleRate: e.sampleRate ?? i.sampleRate, countOnFailure: e.countOnFailure ?? i.countOnFailure, overflowStrategy: e.overflowStrategy ?? i.overflowStrategy, allowKey: e.allowKey ?? (() => !0) }; } /** * Wrap a fetch call. If the guard blocks, it throws ApiRunawayBlockedError * (or queues/drops according to strategy). */ async guardFetch(e, s) { const t = this.cfg.keyFn(e, s); if (!this.cfg.allowKey(t)) return fetch(e, s); await this.beforeRequest(t); let o; try { return o = await fetch(e, s), this.afterRequest(t, !0), o; } catch (a) { throw this.afterRequest(t, this.cfg.countOnFailure), a; } } /** * Generic guard for arbitrary async API calls (Axios, graphql-request, etc.) * Usage: * await apiGuard.guard('POST /api/items', () => axios.post(...)); */ async guard(e, s) { if (!this.cfg.allowKey(e)) return s(); await this.beforeRequest(e); try { const t = await s(); return this.afterRequest(e, !0), t; } catch (t) { throw this.afterRequest(e, this.cfg.countOnFailure), t; } } /** Clear counters for testing or when navigating away. */ reset(e) { e ? this.buckets.delete(e) : this.buckets.clear(); } /* ----------------- internals ----------------- */ getBucket(e) { let s = this.buckets.get(e); return s || (s = { hits: [], blockedUntil: 0, lastReportAt: 0 }, this.buckets.set(e, s)), s; } async beforeRequest(e) { const s = Date.now(), t = this.getBucket(e); if (this.prune(s, t), t.blockedUntil > s) if (this.cfg.overflowStrategy === "queue") await this.waitUntil(t.blockedUntil, t); else { if (this.cfg.overflowStrategy === "drop") return this.maybeReport( e, s, t, /*reason*/ "blocked_active" ), Promise.resolve(void 0); throw this.maybeReport(e, s, t, "blocked_active"), new n(e, this.cfg, t); } if (t.hits.push(s), this.prune(s, t), t.hits.length > this.cfg.maxRequests) if (t.blockedUntil = s + this.cfg.blockDurationMs, this.maybeReport(e, s, t, "threshold_exceeded"), this.cfg.overflowStrategy === "queue") t.hits.pop(), await this.waitUntil(t.blockedUntil, t), t.hits.push(Date.now()), this.prune(Date.now(), t); else { if (this.cfg.overflowStrategy === "drop") return t.hits.pop(), Promise.resolve(void 0); throw t.hits.pop(), new n(e, this.cfg, t); } } afterRequest(e, s) { s || this.getBucket(e).hits.pop(); } prune(e, s) { const t = e - this.cfg.windowMs; for (; s.hits.length && s.hits[0] < t; ) s.hits.shift(); } maybeReport(e, s, t, o) { s - t.lastReportAt < this.cfg.reportDebounceMs || (t.lastReportAt = s, l(this.cfg.sampleRate) && h( "api_runaway_blocked", { // "threshold_exceeded" or "blocked_active" window_ms: this.cfg.windowMs, max_requests: this.cfg.maxRequests, block_ms: this.cfg.blockDurationMs, count_in_window: t.hits.length }, this.cfg.sampleRate )); } waitUntil(e, s) { return Date.now() >= e ? Promise.resolve() : (s.waiters || (s.waiters = []), new Promise((t) => { s.waiters.push(t); const o = e - Date.now(); setTimeout(() => { const a = s.waiters; s.waiters = [], a.forEach((c) => c()); }, o); })); } } class n extends Error { constructor(e, s, t) { super( `API requests blocked by guard for key "${e}" until ${new Date( t.blockedUntil ).toISOString()}` ), this.name = "ApiRunawayBlockedError", this.key = e, this.until = t.blockedUntil, this.windowMs = s.windowMs, this.maxRequests = s.maxRequests; } } function u(r, e) { const s = (e?.method ?? "GET").toUpperCase(); try { const t = typeof r == "string" ? new URL(r, location.origin) : new URL(r.toString(), location.origin); return `${s} ${t.pathname}`; } catch { return `${s} ${String(r)}`; } } function l(r) { return Math.random() * 100 < Math.max(0, Math.min(100, Math.round(r))); } export { f as A, n as a }; //# sourceMappingURL=rateGuard-DVhJO_z1.js.map