Documentation Index
Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
Use this file to discover all available pages before exploring further.
Error Handling Patterns
This page covers advanced error handling — classification, telemetry, structured logging, and circuit breakers. For the basics (try/catch, retry, re-auth, user-friendly messages), see the Error Handling guide.
Error Classification
SdkError is a tagged union — every variant has a stable tag you can read at runtime. Unlike the iOS / Android SDKs (which ship a category extension in their Defaults libraries), the React Native binding leaves classification to you. This three-line helper mirrors the categories used on the other platforms:
import { SdkError, SdkError_Tags } from '@sunnyside-io/privacy-boost-react-native';
export type ErrorCategory =
| 'Auth'
| 'Network'
| 'Validation'
| 'Permission'
| 'RateLimit'
| 'Server'
| 'Internal';
export function categoryOf(err: unknown): ErrorCategory {
if (!isSdkError(err)) return 'Internal';
const tag = (err as { tag?: string }).tag;
switch (tag) {
case SdkError_Tags.NotAuthenticated:
case SdkError_Tags.AuthenticationFailed:
case SdkError_Tags.AuthServerError:
return 'Auth';
case SdkError_Tags.NetworkError:
return 'Network';
case SdkError_Tags.InvalidConfig:
case SdkError_Tags.ConfigError:
case SdkError_Tags.InvalidAddress:
case SdkError_Tags.InvalidAmount:
case SdkError_Tags.InvalidInput:
case SdkError_Tags.InsufficientBalance:
return 'Validation';
case SdkError_Tags.Forbidden:
case SdkError_Tags.SignatureRejected:
return 'Permission';
case SdkError_Tags.RateLimited:
return 'RateLimit';
case SdkError_Tags.ShieldError:
case SdkError_Tags.TransferError:
case SdkError_Tags.NoteError:
case SdkError_Tags.MerkleError:
case SdkError_Tags.ApiError:
case SdkError_Tags.ResourceNotFound:
return 'Server';
default:
return 'Internal';
}
}
export function isRetryable(err: unknown): boolean {
if (!isSdkError(err)) return false;
const tag = (err as { tag?: string }).tag;
if (tag === SdkError_Tags.NetworkError || tag === SdkError_Tags.RateLimited) {
return true;
}
if (tag === SdkError_Tags.ApiError) {
return Boolean((err as { retryable?: boolean }).retryable);
}
return false;
}
function isSdkError(err: unknown): boolean {
// Every SdkError variant is constructable as a class — instanceOf checks below.
return (
SdkError.NotConnected.instanceOf(err) ||
SdkError.NotAuthenticated.instanceOf(err) ||
SdkError.NetworkError.instanceOf(err) ||
SdkError.WalletError.instanceOf(err) ||
SdkError.InsufficientBalance.instanceOf(err) ||
SdkError.InvalidAddress.instanceOf(err) ||
SdkError.InvalidAmount.instanceOf(err) ||
SdkError.SerializationError.instanceOf(err) ||
SdkError.AuthServerError.instanceOf(err) ||
SdkError.ShieldError.instanceOf(err) ||
SdkError.TransferError.instanceOf(err) ||
SdkError.NoteError.instanceOf(err) ||
SdkError.MerkleError.instanceOf(err) ||
SdkError.ApiError.instanceOf(err) ||
SdkError.RateLimited.instanceOf(err) ||
SdkError.Forbidden.instanceOf(err) ||
SdkError.ResourceNotFound.instanceOf(err) ||
SdkError.InternalError.instanceOf(err) ||
SdkError.SignatureRejected.instanceOf(err)
);
}
| Category | Meaning | Recommended UI | Retry? |
|---|
Auth | Session/auth flow failed | Re-prompt sign-in | No (but re-auth and try once) |
Network | Connectivity / DNS / timeout | ”Check your connection” | Yes, with backoff |
Validation | Input is malformed | Inline form error | No |
Permission | User cancelled / forbidden | Silent dismiss or contact support | No |
RateLimit | Too many requests | Wait + auto-retry | Yes, after retryAfterMs |
Server | Backend / proof / merkle failure | Generic error toast | Sometimes (check isRetryable) |
Internal | Bug or invariant violation | ”Something went wrong” + report | No |
Structured Logging
Log SDK errors with stable fields so they’re queryable in your log pipeline (Logcat, Console, Datadog, etc.).
import { SdkError, SdkError_Tags } from '@sunnyside-io/privacy-boost-react-native';
function variantTag(err: unknown): string {
const tag = (err as { tag?: string }).tag;
if (!tag) return 'Other';
if (tag === SdkError_Tags.ApiError) return `ApiError.${(err as { code?: string }).code ?? '?'}`;
if (tag === SdkError_Tags.ShieldError) return `ShieldError.${(err as { code?: string }).code ?? '?'}`;
if (tag === SdkError_Tags.TransferError) return `TransferError.${(err as { code?: string }).code ?? '?'}`;
return tag;
}
export function logError(operation: string, err: unknown) {
const category = categoryOf(err);
const variant = variantTag(err);
const retry = isRetryable(err);
console.error(`[PB] [${operation}] category=${category} retryable=${retry} variant=${variant}`, err);
}
If your message field could include PII, scrub or omit it from logs.
Error Reporting Integration
Sentry
import * as Sentry from '@sentry/react-native';
export function reportSdk(err: unknown, operation: string) {
Sentry.withScope((scope) => {
scope.setTag('pb.operation', operation);
scope.setTag('pb.category', categoryOf(err));
scope.setTag('pb.retryable', String(isRetryable(err)));
scope.setTag('pb.variant', variantTag(err));
Sentry.captureException(err);
});
}
The pb.category tag lets you build dashboards that group errors by class — “auth-class errors are spiking” is more actionable than “twelve different variants are spiking.”
Filter Out Noise
User cancellations (SignatureRejected) and validation errors aren’t bugs — don’t ship them to your error tracker:
export function shouldReport(err: unknown): boolean {
const cat = categoryOf(err);
return cat !== 'Permission' && cat !== 'Validation';
}
Operation-Scoped Wrapping
Wrap each meaningful SDK call so logging, reporting, and retry sit in one place.
export async function runSdk<T>(
operation: string,
block: () => Promise<T>,
): Promise<T> {
try {
return await block();
} catch (err) {
logError(operation, err);
if (shouldReport(err)) reportSdk(err, operation);
throw err;
}
}
// Usage
const result = await runSdk('shield', () =>
sdk.shield(token, amount),
);
Circuit Breaker
If the backend is degraded, hammering it with retries makes things worse. Wrap operations in a circuit breaker that trips after consecutive server-class failures.
type BreakerState =
| { kind: 'closed' }
| { kind: 'open'; until: number }
| { kind: 'halfOpen' };
class CircuitBreaker {
private state: BreakerState = { kind: 'closed' };
private consecutive = 0;
constructor(
private readonly threshold = 5,
private readonly cooldownMs = 60_000,
) {}
canPass(): boolean {
if (this.state.kind === 'closed' || this.state.kind === 'halfOpen') return true;
if (Date.now() >= this.state.until) {
this.state = { kind: 'halfOpen' };
return true;
}
return false;
}
recordSuccess() {
this.consecutive = 0;
this.state = { kind: 'closed' };
}
recordFailure(err: unknown) {
const cat = categoryOf(err);
if (cat !== 'Server' && cat !== 'Network') return;
this.consecutive++;
if (this.consecutive >= this.threshold) {
this.state = { kind: 'open', until: Date.now() + this.cooldownMs };
}
}
}
const breaker = new CircuitBreaker();
export async function guarded<T>(block: () => Promise<T>): Promise<T> {
if (!breaker.canPass()) {
throw new Error('circuit open');
}
try {
const result = await block();
breaker.recordSuccess();
return result;
} catch (err) {
breaker.recordFailure(err);
throw err;
}
}
Auth/validation/permission failures should not trip the breaker — they’re user-class problems, not service degradation. The classification by categoryOf makes that distinction trivial.
Telemetry: Operation Latency by Outcome
Pair errors with timings — slow successes are interesting too.
interface OpMetric {
operation: string;
durationMs: number;
outcome: string;
}
export async function instrument<T>(
operation: string,
sink: (m: OpMetric) => void,
block: () => Promise<T>,
): Promise<T> {
const start = performance.now();
try {
const result = await block();
sink({ operation, durationMs: performance.now() - start, outcome: 'success' });
return result;
} catch (err) {
sink({ operation, durationMs: performance.now() - start, outcome: categoryOf(err) });
throw err;
}
}
Forward OpMetric to Sentry Performance, Datadog RUM, Firebase Performance, or your own analytics pipeline.
Best Practices
- Branch on
categoryOf(err), not on individual variants — UI logic stays small and stable across SDK upgrades.
- Filter user-class errors out of error reporters —
Permission and Validation are noise.
- Tag every report with
pb.category and pb.variant — makes dashboards actually useful.
- Trip circuit breakers only on
Server and Network — never on auth or validation.
- Pair errors with timings — a slow success that turns into a timeout is worth catching upstream.
Next Steps