@fxl-business/support-sdk
v0.2.0
Published
Error tracking SDK for FXL Support — frontend + backend (Hono middleware)
Readme
@fxl-business/support-sdk
Error tracker for FXL apps. Captures frontend runtime errors (window.onerror, unhandledrejection, breadcrumbs, console) and backend exceptions (Hono middleware) and ships them to the FXL Support API.
Install
pnpm add @fxl-business/support-sdkFrontend (browser)
import { initErrorTracker } from '@fxl-business/support-sdk'
const cleanup = initErrorTracker({
apiUrl: 'https://support-api.fxlbusinessschool.com.br',
projectApiKey: import.meta.env.VITE_FXL_SUPPORT_KEY,
appVersion: '1.2.3',
getUser: () => ({ name: currentUser?.name, email: currentUser?.email }),
})
// call cleanup() on unmount if you need to tear it downCaptures:
- Uncaught
ErrorEventfromwindow.onerror unhandledrejectionevents- Console
warn/error(last 20) - Breadcrumbs (navigation, clicks, fetch)
Events are batched and flushed on beforeunload.
React error boundary
import { FxlErrorBoundary } from '@fxl-business/support-sdk'
<FxlErrorBoundary fallback={<ErrorPage />}>
<App />
</FxlErrorBoundary>Backend (Hono)
Two import paths — both work:
import { errorTrackerMiddleware } from '@fxl-business/support-sdk'
// or equivalently, the dedicated subpath:
import { errorTrackerMiddleware } from '@fxl-business/support-sdk/hono'
import { Hono } from 'hono'
const app = new Hono()
app.use(
'*',
errorTrackerMiddleware({
apiUrl: 'https://support-api.fxlbusinessschool.com.br',
projectApiKey: process.env.FXL_SUPPORT_KEY!,
appVersion: process.env.APP_VERSION,
}),
)
app.get('/boom', () => {
throw new Error('something blew up')
})The middleware catches any error thrown downstream, ships the payload (with sanitized headers and request body, 4KB cap), and re-throws so Hono's own error handling continues unchanged. Sending is fire-and-forget — a failure to report never crashes the host.
Sensitive headers stripped automatically: authorization, cookie, set-cookie, x-api-key, x-forwarded-for, x-real-ip.
Backend proxy (Hono)
In the v1.3 proxy model, user-scoped widget actions (list / view / close / reopen
tickets, and request presigned R2 upload URLs) go through the customer backend
so the secret key never leaves the server. createSupportProxy returns a
mountable Hono router that does the forwarding for you:
import { Hono } from 'hono'
import { createSupportProxy } from '@fxl-business/support-sdk/hono'
// or, equivalently, from the main barrel:
// import { createSupportProxy } from '@fxl-business/support-sdk'
const app = new Hono()
app.route('/api/support', createSupportProxy({
apiUrl: process.env.FXL_SUPPORT_API_URL!,
secretKey: process.env.FXL_SUPPORT_SECRET_KEY!,
getUserEmail: async (c) => c.get('user')?.email ?? null,
}))The mount point (here /api/support) is the value you pass to the widget's
proxyUrl prop. The router exposes exactly 5 routes:
| Proxy route | Forwards to |
|---|---|
| POST /upload-url | POST ${apiUrl}/api/v1/tickets/:ticketId/upload |
| GET /tickets | GET ${apiUrl}/api/v1/widget/tickets (query string preserved) |
| GET /tickets/:id | GET ${apiUrl}/api/v1/widget/tickets/:id |
| PATCH /tickets/:id/close | PATCH ${apiUrl}/api/v1/widget/tickets/:id/close |
| PATCH /tickets/:id/reopen | PATCH ${apiUrl}/api/v1/widget/tickets/:id/reopen |
Every forwarded request carries X-API-Secret (from options.secretKey) and
X-Reporter-Email (resolved per-request via options.getUserEmail(c)).
Upstream JSON bodies and status codes are passed through unchanged.
If getUserEmail(c) returns null, undefined, or an empty string, the proxy
responds with HTTP 401 — no upstream call is made. A forgetful host app gets
a loud error rather than a confusing "X-Reporter-Email required" from the
Support API.
Not proxied: POST /errors (SDK error reporting) and POST /tickets
(widget ticket submission) are publishable-key-direct from the customer
frontend by design — they use your publishable key as X-API-Key against the
Support API and do not need a backend proxy.
For non-Hono backends (Express, Next.js route handlers, Fastify, Hapi), hand-write the same 5 routes with the same header injection. The contract is transport-agnostic; only the mount syntax differs.
Fingerprinting
Fingerprints are computed authoritatively on the server (SHA-256 of normalized message|stackFrames). The SDK does not compute or send fingerprints — the server groups events.
License
Private — FXL internal.
