Error Handling

Guide to error handling patterns in the Deva SDK across components, authentication, and data fetching.


Overview

The SDK handles errors at multiple levels:

  1. Authentication Errors - OAuth flow failures, token issues

  2. Network Errors - API request failures, timeouts

  3. Streaming Errors - SSE connection issues, parsing errors

  4. Component Errors - Channel not found, post creation failures

  5. Validation Errors - Invalid input, missing required fields


Authentication Errors

OpenID Configuration Load Failure

Occurs when SDK cannot load OAuth configuration.

Causes:

  • Network connectivity issues

  • Invalid environment configuration

  • Server unavailable

Behavior:

// Provider.tsx
const { openIdConfig, error } = useOpenIdConfig(env);
if (error) {
  console.error("[sdk/provider] error fetching openid info: ", error);
}

Impact:

  • Provider initialization fails

  • Users cannot authenticate

  • App cannot use SDK features

Resolution:

  • Check network connectivity

  • Verify environment setting

  • Check Deva platform status


Authorization Code Exchange Failure

Occurs during OAuth callback when exchanging code for tokens.

Causes:

  • Invalid authorization code

  • Code already used

  • PKCE verification failure

  • Network timeout

Behavior:

const error_description = params.get("error_description")
  ?? "An unknown error occurred";
console.error(`[sdk/provider]: ${error_description}`);
setAuthError(error_description);

Detection:

const { authError } = useDeva();

if (authError) {
  // Show error to user
  return <div>Authentication error: {authError}</div>;
}

Resolution:

  • Retry login

  • Clear browser storage

  • Check redirect URI configuration


Token Refresh Failure

Occurs when automatic token refresh fails.

Causes:

  • Refresh token expired

  • Network error

  • Invalid refresh token

  • Server error

Behavior:

try {
  // Refresh token
} catch (error) {
  console.error("[sdk/provider]: Error refreshing token", error);
  // User logged out automatically
}

Impact:

  • User automatically logged out

  • Session cleared

  • Must re-authenticate

Prevention:

  • Don't manually clear sessionStorage

  • Maintain active browser session

  • Avoid long periods of inactivity


Network Errors

Data Fetching Errors

SWR automatically handles network errors with retries.

Error Exposure:

const { data, error, isLoading } = useSWRFetcher<T>(url);

if (error) {
  // Handle error
  return <ErrorDisplay message={error.message} />;
}

Retry Behavior:

  • Automatic retries with exponential backoff

  • Continues using stale data if available

  • Error state exposed via error property

Example:

function ChannelData({ handle }: { handle: string }) {
  const { url, accessToken } = useDeva();
  const { data, error, isLoading } = useSWRFetcher<Channel>(
    `${url}/api/sdk/channel/handle/${handle}`
  );

  if (error) {
    return <div>Failed to load channel: {error.message}</div>;
  }

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return <div>{data?.name}</div>;
}

API Request Failures

Manual API calls require explicit error handling.

Pattern:

try {
  const response = await fetcher<Post>(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify(data),
  });
  return response;
} catch (error) {
  console.error("Error creating post:", error);
  throw error; // Re-throw or handle
}

Component Usage:

const [error, setError] = useState<string | null>(null);

const handleSubmit = async () => {
  try {
    setError(null);
    await createPost(text);
  } catch (err) {
    setError("Failed to create post. Please try again.");
  }
};

return (
  <div>
    {error && <div className="error">{error}</div>}
    <button onClick={handleSubmit}>Submit</button>
  </div>
);

Streaming Errors

Stream Connection Failure

Occurs when SSE connection cannot be established.

Causes:

  • Network connectivity issues

  • Invalid message/thread ID

  • Authentication failure

  • Server error

Detection:

const { message, isStreaming, error, isDone } = useStreamResponse({
  messageId,
  threadId
});

if (error) {
  return <MessageStreamError text={error.message} />;
}

Error Component:

// MessageStreamError component
<div className="error-container">
  <div className="error-icon">🚧</div>
  <div className="error-content">
    <p className="error-title">Problem</p>
    <p className="error-message">{error.message}</p>
  </div>
</div>

Behavior:

onopen: async (response) => {
  if (response.ok) {
    log("Stream connection opened");
  } else {
    throw new Error(
      `Failed to open stream: ${response.status} ${response.statusText}`
    );
  }
}

Stream Parsing Errors

Occurs when stream event data cannot be parsed.

Causes:

  • Malformed JSON

  • Unexpected event format

  • Encoding issues

Handling:

onmessage: (msg) => {
  try {
    if (msg.event === "content") {
      const chunk = JSON.parse(msg.data);
      // Process chunk
    }
  } catch (err) {
    console.error("Error parsing stream data:", err);
    setError(
      err instanceof Error
        ? err
        : new Error("Error parsing stream data")
    );
  }
}

Recovery:

  • Error displayed to user

  • Stream aborted

  • User can retry by sending new message


Stream Interruption

Occurs when connection is lost during streaming.

Causes:

  • Network disconnection

  • Server timeout

  • Component unmount

  • User navigation

Cleanup:

useEffect(() => {
  // Setup stream...

  return () => {
    // Cleanup on unmount
    if (controllerRef.current) {
      controllerRef.current.abort();
      controllerRef.current = null;
    }
    setIsStreaming(false);
  };
}, [messageId, threadId]);

Graceful Handling:

onerror: (err: Error) => {
  console.error("Stream error:", err);
  setError(new Error(err.message || "Error streaming message"));
  setIsStreaming(false);
}

Component-Specific Errors

ChannelFeed Errors

Channel Not Found:

const [channelError, setChannelError] = useState<string | null>(null);

useEffect(() => {
  if (channelFetchError) {
    setChannelError("Channel not found");
  } else {
    setChannelError(null);
  }
}, [channelFetchError]);

// Display
if (channelError) {
  return <div className="error">Channel not found</div>;
}

Post Creation Failure:

const [error, setError] = useState<string | null>(null);

const handleCreatePost = async (text: string) => {
  try {
    setError(null);
    await createPost(text);
    setInputValue("");
  } catch (err) {
    setError("Failed to create post. Please try again.");
  }
};

// Display error in UI
{error && (
  <div className="error-banner">
    {error}
  </div>
)}

Intercom Errors

Deva Not Found:

const { data: personas } = useSWRFetcher<PaginatedPersonas>(
  `${url}/api/sdk/persona/public?usernames=${username}`
);

if (personas?.items.length === 0) {
  return <div>Deva not found</div>;
}

Thread Creation Failure:

const { data: thread, error } = useSWRFetcher<Thread>(
  `${url}/api/sdk/chat/dm/${username}`
);

if (error) {
  return <div>Failed to load conversation: {error.message}</div>;
}

Message Send Failure:

  • Handled internally by Intercom

  • Error displayed to user

  • Allows retry


Error Boundaries

React Error Boundary Pattern

Recommended for production apps:

import { Component, ErrorInfo, ReactNode } from "react";

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("DevaProvider error:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => window.location.reload()}>
            Reload
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <DevaProvider {...config}>
    <App />
  </DevaProvider>
</ErrorBoundary>

Best Practices

1. Always Handle Errors

Bad:

const { data } = useSWRFetcher<T>(url);
return <div>{data.value}</div>; // Crashes if data is undefined

Good:

const { data, error, isLoading } = useSWRFetcher<T>(url);

if (error) return <ErrorDisplay error={error} />;
if (isLoading) return <Loading />;
if (!data) return null;

return <div>{data.value}</div>;

2. Provide User Feedback

Bad:

try {
  await action();
} catch (err) {
  console.error(err); // User sees nothing
}

Good:

const [error, setError] = useState<string | null>(null);

try {
  setError(null);
  await action();
} catch (err) {
  setError("Action failed. Please try again.");
}

return (
  <div>
    {error && <ErrorBanner message={error} />}
  </div>
);

3. Implement Retry Logic

const [retryCount, setRetryCount] = useState(0);

const handleRetry = () => {
  setRetryCount(count => count + 1);
  setError(null);
};

return (
  <div>
    {error && (
      <div>
        <p>{error.message}</p>
        <button onClick={handleRetry}>Retry</button>
      </div>
    )}
  </div>
);

4. Log Errors Appropriately

// Development: Log details
if (process.env.NODE_ENV === "development") {
  console.error("Detailed error:", error);
}

// Production: Log to monitoring service
if (process.env.NODE_ENV === "production") {
  errorMonitoringService.log(error);
}

5. Handle Edge Cases

// Check authentication before actions
if (!isAuthenticated) {
  return <LoginPrompt />;
}

// Validate input
if (!text.trim()) {
  setError("Message cannot be empty");
  return;
}

// Check required data
if (!thread) {
  return <div>Loading conversation...</div>;
}

Common Error Types

Authentication Errors

Error
Cause
Resolution

OpenID config load failure

Network/server issue

Check connectivity, retry

Code exchange failure

Invalid auth code

Retry login

Token refresh failure

Expired refresh token

Re-authenticate

Invalid credentials

Wrong client ID

Check configuration

Network Errors

Error
Cause
Resolution

401 Unauthorized

Invalid/expired token

Re-authenticate

403 Forbidden

Insufficient permissions

Check user access

404 Not Found

Resource doesn't exist

Verify ID/handle

500 Server Error

Server issue

Retry, contact support

Network timeout

Slow connection

Retry with backoff

Streaming Errors

Error
Cause
Resolution

Connection refused

Server unavailable

Retry later

Stream timeout

Long response time

Increase timeout

Parse error

Malformed data

Report to support

Connection dropped

Network interruption

Retry message


Error Response Format

API errors typically follow this format:

{
  error: string;           // Error type
  message: string;         // Human-readable message
  status: number;          // HTTP status code
  details?: any;           // Additional context
}

Last updated