npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ethora/chat-component

v26.3.26

Published

1. npm create vite@latest 2. select name of project, select type (react/js) 3. cd project-name 4. npm i 5. npm i @ethora/chat-component 6. go to file src/App.tsx and replace it with this code

Readme

Ethora Chat Component (@ethora/chat-component)

GitHub watchers GitHub forks GitHub Repo stars GitHub repo size GitHub language count GitHub top language GitHub commit activity (branch) GitHub issues GitHub closed issues GitHub GitHub contributors

JavaScript React TypeScript JWT

Discord Twitter URL Website YouTube Channel Subscribers

React + TypeScript chat UI component powered by Ethora backend APIs and XMPP.
Use it as a standalone chat page, as an embedded widget in your existing app, or as a customizable chat foundation with your own auth and UI.

Table of Contents

Overview

@ethora/chat-component gives you a production-oriented chat interface with:

  • Room list and room chat UI
  • Message history, replies, reactions, edits, deletes
  • Typing indicators
  • In-app notifications + Web Push integration
  • Configurable auth modes (default/login form/google/jwt/custom user)
  • Custom render components for message/input/scroll/day separator/new-message label

The package exports:

  • Chat (main component)
  • XmppProvider
  • useUnread
  • logoutService
  • useQRCodeChat, handleQRChatId
  • useInAppNotifications
  • usePushNotifications
  • resendMessage

Why Ethora

Ethora provides hosted and customizable messaging infrastructure plus a wider product ecosystem.

| Dimension | Ethora Chat Component | Full Ethora Platform | | --- | --- | --- | | Primary goal | Embed chat quickly in a React app | End-to-end product stack (chat, profiles, wallets, AI, admin) | | Time to first chat | Minutes | Higher initial setup, broader capabilities | | Frontend scope | Focused web chat UI package | Multi-product ecosystem and broader SDK/tooling | | Custom UI control | High via props + custom components | High, with additional platform-specific tooling | | Best fit | Support chat, portal messaging, embedded chat widget | Full social/messaging app platforms with extended modules |

Quick Start

1. Install

| Tool | Command | | --- | --- | | npm | npm i @ethora/chat-component | | yarn | yarn add @ethora/chat-component | | pnpm | pnpm add @ethora/chat-component | | bun | bun add @ethora/chat-component |

2. Render the chat

import { Chat, XmppProvider } from '@ethora/chat-component';
import './App.css';

export default function App() {
  return (
    <XmppProvider>
      <Chat />
    </XmppProvider>
  );
}

Required wrapper (XmppProvider)

Chat relies on internals that use useXmppClient(). In real integrations, wrap Chat (or your entire app shell) with XmppProvider:

import { Chat, XmppProvider } from '@ethora/chat-component';

export default function App() {
  return (
    <XmppProvider>
      <Chat config={{ baseUrl: 'https://api.chat.ethora.com/v1' }} />
    </XmppProvider>
  );
}

XmppProvider also accepts a pushNotifications prop for headless push setup (works even if Chat is not rendered yet):

import { XmppProvider } from '@ethora/chat-component';

export default function App() {
  return (
    <XmppProvider
      config={{ baseUrl: 'https://api.chat.ethora.com/v1', initBeforeLoad: true }}
      pushNotifications={{
        enabled: true,
        softAsk: false,
        vapidPublicKey: 'PLACEHOLDER_VAPID_PUBLIC_KEY',
        iconPath: '/icons/push-icon-192.png',
        badgePath: '/icons/push-badge-72.png',
        firebaseConfig: {
          apiKey: 'PLACEHOLDER_API_KEY',
          authDomain: 'PLACEHOLDER_AUTH_DOMAIN',
          projectId: 'PLACEHOLDER_PROJECT_ID',
          storageBucket: 'PLACEHOLDER_STORAGE_BUCKET',
          messagingSenderId: 'PLACEHOLDER_MESSAGING_SENDER_ID',
          appId: 'PLACEHOLDER_APP_ID',
        },
      }}
    >
      {/* Chat can be mounted later or omitted */}
      <div>App shell</div>
    </XmppProvider>
  );
}

Single XMPP Initialization Contract

To avoid duplicated wss://.../ws connections, keep a single XMPP init source:

  • initBeforeLoad: true -> XmppProvider is the only place that initializes XMPP.
  • initBeforeLoad: false -> Chat (useChatWrapperInit) initializes XMPP.

If your app also has external client.login(...) logic, guard it:

if (chatConfig.initBeforeLoad) {
  console.warn('[XMPP] initBeforeLoad=true, skip external client.login()');
  return;
}

await client.login(...);

Also pass a memoized config object to both XmppProvider and Chat:

const chatConfig = useMemo(() => ({ ...baseConfig }), [baseConfig]);

<XmppProvider config={chatConfig}>
  <Chat config={chatConfig} />
</XmppProvider>;

3. Run

npm run dev

Open http://localhost:5173.

Integration Modes

All modes below assume XmppProvider wraps Chat.

A) Minimal demo mode (provider + chat)

<XmppProvider>
  <Chat />
</XmppProvider>

Useful for local proof-of-concept and quick UI validation.

B) Auto default credential fallback (legacy behavior)

If no googleLogin, no jwtLogin, no userLogin, and no defaultLogin, LoginWrapper currently triggers internal email/password fallback logic.

<XmppProvider>
  <Chat config={{ colors: { primary: '#2563eb', secondary: '#dbeafe' } }} />
</XmppProvider>

C) Explicit email/password via user prop

<XmppProvider>
  <Chat
    user={{
      email: '[email protected]',
      password: 'PLACEHOLDER_PASSWORD',
    }}
  />
</XmppProvider>

D) Injected logged-in user (userLogin)

<XmppProvider>
  <Chat
    config={{
      userLogin: {
        enabled: true,
        user: {
          _id: 'PLACEHOLDER_USER_ID',
          appId: 'PLACEHOLDER_APP_ID',
          walletAddress: 'PLACEHOLDER_WALLET_ADDRESS',
          defaultWallet: { walletAddress: 'PLACEHOLDER_WALLET_ADDRESS' },
          firstName: 'Jane',
          lastName: 'Doe',
          xmppPassword: 'PLACEHOLDER_XMPP_PASSWORD',
          token: 'PLACEHOLDER_ACCESS_TOKEN',
          refreshToken: 'PLACEHOLDER_REFRESH_TOKEN',
          username: 'PLACEHOLDER_USERNAME',
        },
      },
    }}
  />
</XmppProvider>

E) JWT login

<XmppProvider>
  <Chat
    config={{
      jwtLogin: {
        enabled: true,
        token: 'PLACEHOLDER_JWT_TOKEN',
      },
    }}
  />
</XmppProvider>

F) Google login

<XmppProvider>
  <Chat
    config={{
      googleLogin: {
        enabled: true,
        firebaseConfig: {
          apiKey: 'PLACEHOLDER_API_KEY',
          authDomain: 'PLACEHOLDER_AUTH_DOMAIN',
          projectId: 'PLACEHOLDER_PROJECT_ID',
          storageBucket: 'PLACEHOLDER_STORAGE_BUCKET',
          messagingSenderId: 'PLACEHOLDER_MESSAGING_SENDER_ID',
          appId: 'PLACEHOLDER_APP_ID',
        },
      },
    }}
  />
</XmppProvider>

G) Single-room entry + URL/QR behavior

<XmppProvider>
  <Chat
    roomJID="[email protected]"
    config={{
      setRoomJidInPath: true,
      qrUrl: 'https://your-app.example/chat/?qrChatId=',
    }}
  />
</XmppProvider>

roomJID forces entry room.
setRoomJidInPath syncs room identity to URL path.
useQRCodeChat / handleQRChatId support QR/deep-link room opening.

Behavior Notes and Legacy Quirks

  • newArch is now default-on. If omitted, runtime uses new architecture paths.
  • Old architecture is used only when you explicitly set config.newArch = false.
  • defaultLogin currently has legacy inverted behavior in LoginWrapper:
    • internal fallback login runs when login modes are not configured and defaultLogin is not set.
    • keep this in mind when migrating; prefer explicit userLogin / jwtLogin / googleLogin.

Chat Props Reference

These are the top-level props accepted by Chat (exported from ReduxWrapper).

| Prop | Type | Required | Notes | | --- | --- | --- | --- | | config | IConfig | No | Main behavior/configuration object. | | roomJID | string | No | Force specific room JID on load. | | user | { email: string; password: string } | No | Credentials for email/password login helper path. | | loginData | { email: string; password: string } | No | Optional login payload. | | MainComponentStyles | React.CSSProperties | No | Outer container style override. | | token | string | No | Optional token input (legacy/integration-specific usage). | | CustomMessageComponent | React.ComponentType<MessageProps> | No | Replace message bubble rendering. | | CustomInputComponent | React.ComponentType<SendInputProps & { onSendMessage?; onSendMedia?; placeholderText?; }> | No | Replace chat input area. | | CustomScrollableArea | React.ComponentType<CustomScrollableAreaProps> | No | Replace list/scroll wrapper behavior. | | CustomDaySeparator | React.ComponentType<DaySeparatorProps> | No | Replace day separator node. | | CustomNewMessageLabel | React.ComponentType<NewMessageLabelProps> | No | Replace "new message" marker. |

Full Config Reference (IConfig)

Below is a grouped reference for all config options.

Core

| Option | Type | Description | | --- | --- | --- | | appId | string | App identifier for backend context. | | baseUrl | string | API base URL (defaults to https://api.chat.ethora.com/v1, the Ethora Cloud production endpoint). | | customAppToken | string | Custom app token for API initialization. | | xmppSettings | { devServer; host; conference?; xmppPingOnSendEnabled? } | XMPP connectivity settings. | | initBeforeLoad | boolean | Initialize XMPP before normal chat load flow. | | clearStoreBeforeInit | boolean | Clear local store before initialization. | | newArch | boolean | Defaults to true; set false to explicitly force legacy/old architecture paths. | | useStoreConsoleEnabled | boolean | Enable verbose internal logging in console. |

UI and Layout

| Option | Type | Description | | --- | --- | --- | | disableHeader | boolean | Hide chat header. | | disableMedia | boolean | Disable media sending/processing paths. | | disableRooms | boolean | Hide/disable room list area. | | disableRoomMenu | boolean | Disable room menu controls. | | disableRoomConfig | boolean | Disable room configuration actions. | | disableNewChatButton | boolean | Hide new chat/create room action. | | disableUserCount | boolean | Hide user count in header/UI. | | disableChatInfo | { disableHeader?; disableDescription?; disableType?; disableMembers?; hideMembers?; disableChatHeaderMenu? } | Fine-grained chat info panel toggles. | | chatHeaderBurgerMenu | boolean | Toggle burger menu in chat header. | | chatHeaderSettings | { hide?; disableCreate?; disableMenu?; hideSearch? } | Additional header-level controls. | | chatHeaderAdditional | { enabled: boolean; element: any } | Inject custom element into header area. | | headerLogo | string \| React.ReactElement | Custom logo in header. | | headerMenu | () => void | Custom menu handler. | | headerChatMenu | () => void | Custom room header menu handler. | | colors | { primary: string; secondary: string } | Theme colors for component UI. | | roomListStyles | React.CSSProperties | Styles for room list pane. | | chatRoomStyles | React.CSSProperties | Styles for chat pane. | | noMessagesPlaceholder | React.ComponentType | Replace empty-chat placeholder component (same render position as default placeholder). | | backgroundChat | { color?: string; image?: string \| File } | Chat background customization. | | bubleMessage | MessageBubble | Bubble-level style overrides (as defined in types). | | setRoomJidInPath | boolean | Sync room JID to URL path. | | qrUrl | string | Base URL for QR deep link behavior. |

Auth and Identity

| Option | Type | Description | | --- | --- | --- | | defaultLogin | boolean | Legacy quirk: current runtime fallback behavior is inverted; see Behavior Notes section. | | googleLogin | { enabled: boolean; firebaseConfig: FBConfig } | Google login support via Firebase config. | | jwtLogin | { token: string; enabled: boolean; handleBadlogin?: React.ReactElement } | Log user in using JWT exchange flow. | | userLogin | { enabled: boolean; user: User \| null } | Inject already-authenticated user directly. | | customLogin | { enabled: boolean; loginFunction: () => Promise<User \| null> } | Provide your custom async login function. | | refreshTokens | { enabled: boolean; refreshFunction?: () => Promise<{ accessToken: string; refreshToken?: string } \| null> } | Token refresh strategy. |

Rooms and Data

| Option | Type | Description | | --- | --- | --- | | defaultRooms | ConfigRoom[] | Seed/default rooms. | | customRooms | { rooms: PartialRoomWithMandatoryKeys[]; disableGetRooms?: boolean; singleRoom: boolean } | Fully controlled room source. | | forceSetRoom | boolean | Force room setup path in init flow. | | enableRoomsRetry | { enabled: boolean; helperText: string } | Enable retry UX when rooms fail to load. |

Messaging and Interactions

| Option | Type | Description | | --- | --- | --- | | disableInteractions | boolean | Disable message interaction menu/actions. | | disableProfilesInteractions | boolean | Disable profile interactions from chat UI. | | disableSentLogic | boolean | Disable default sent-state logic when needed. | | secondarySendButton | { enabled: boolean; messageEdit: string; label?: React.ReactNode; buttonStyles?: React.CSSProperties; hideInputSendButton?: boolean; overwriteEnterClick?: true } | Extra send action/button config. | | botMessageAutoScroll | boolean | Force auto-scroll behavior on bot messages. | | messageTextFilter | { enabled: boolean; filterFunction: (text: string) => string } | Transform/filter outgoing message text. | | eventHandlers | { onMessageSent?; onMessageFailed?; onMessageEdited? } | Lifecycle callbacks for message operations. | | translates | { enabled: boolean; translations?: Iso639_1Codes } | Message translation-related options. | | whitelistSystemMessage | string[] | Restrict/render only selected system message types. | | customSystemMessage | React.ComponentType<MessageProps> | Replace system message component renderer. |

Typing and Sending Control

| Option | Type | Description | | --- | --- | --- | | disableTypingIndicator | boolean | Disable typing indicator UI logic. | | customTypingIndicator | { enabled: boolean; text?: string \| ((usersTyping: string[]) => string); position?: 'bottom' \| 'top' \| 'overlay' \| 'floating'; styles?: React.CSSProperties; customComponent?: React.ComponentType<{ usersTyping: string[]; text: string; isVisible: boolean; }> } | Customize typing indicator content and rendering. | | blockMessageSendingWhenProcessing | boolean \| { enabled: boolean; timeout?: number; onTimeout?: (roomJID: string) => void } | Gate sends while processing in-flight state. |

Notifications

| Option | Type | Description | | --- | --- | --- | | inAppNotifications | { enabled?; showInContext?; position?; maxNotifications?; duration?; onClick?; customComponent? } | In-app toast notification behavior and custom rendering. |

Push

| Option | Type | Description | | --- | --- | --- | | pushNotifications.enabled | boolean | Enable browser push subscription flow. | | pushNotifications.vapidPublicKey | string | VAPID public key for push registration. | | pushNotifications.firebaseConfig | FBConfig | Firebase app config for push messaging. | | pushNotifications.serviceWorkerPath | string | Service worker path, default /firebase-messaging-sw.js. | | pushNotifications.serviceWorkerScope | string | Service worker scope, default /. | | pushNotifications.iconPath | string | Custom icon URL/path for OS push notifications. | | pushNotifications.badgePath | string | Custom badge URL/path for OS push notifications. Falls back to iconPath. | | pushNotifications.softAsk | boolean | Do not immediately trigger browser permission prompt. | | pushNotifications.onClick | (params) => void \| Promise<void> | Callback invoked when the user clicks an OS push notification (including cold start via URL marker). |

Custom Widgets and Overrides

You can replace key UI parts without forking the package.

| Override prop | Purpose | | --- | --- | | CustomMessageComponent | Fully custom message bubble/row rendering. | | CustomInputComponent | Custom composer and send controls. | | CustomScrollableArea | Custom scroll/list container (virtualized or custom behavior). | | CustomDaySeparator | Custom date separator component. | | CustomNewMessageLabel | Custom "new message" divider label. |

Example:

import { Chat } from '@ethora/chat-component';
import CustomMessageBubble from './CustomMessageBubble';
import CustomChatInput from './CustomChatInput';
import CustomScrollableArea from './CustomScrollableArea';
import CustomDaySeparator from './CustomDaySeparator';
import CustomNewMessageLabel from './CustomNewMessageLabel';

export default function App() {
  return (
    <Chat
      CustomMessageComponent={CustomMessageBubble}
      CustomInputComponent={CustomChatInput}
      CustomScrollableArea={CustomScrollableArea}
      CustomDaySeparator={CustomDaySeparator}
      CustomNewMessageLabel={CustomNewMessageLabel}
      config={{
        colors: { primary: '#1d4ed8', secondary: '#dbeafe' },
      }}
    />
  );
}

Reference example components in repository:

  • src/examples/customComponents/CustomMessageBubble.tsx
  • src/examples/customComponents/CustomChatInput.tsx
  • src/examples/customComponents/CustomScrollableArea.tsx
  • src/examples/customComponents/CustomDaySeparator.tsx
  • src/examples/customComponents/CustomNewMessageLabel.tsx

Push Notifications

Prerequisites

| Requirement | Why | | --- | --- | | HTTPS origin (or localhost) | Browser push APIs require secure contexts. | | Firebase project | FCM token + push transport setup. | | VAPID public key | Required for web push subscription. | | Service worker file | Required for background notification handling. |

Setup steps

  1. Copy service worker into your app's public assets:
npx @ethora/chat-component ethora-chat
  1. Configure push in config:
<Chat
  config={{
    pushNotifications: {
      enabled: true,
      vapidPublicKey: 'PLACEHOLDER_VAPID_PUBLIC_KEY',
      firebaseConfig: {
        apiKey: 'PLACEHOLDER_API_KEY',
        authDomain: 'PLACEHOLDER_AUTH_DOMAIN',
        projectId: 'PLACEHOLDER_PROJECT_ID',
        storageBucket: 'PLACEHOLDER_STORAGE_BUCKET',
        messagingSenderId: 'PLACEHOLDER_MESSAGING_SENDER_ID',
        appId: 'PLACEHOLDER_APP_ID',
      },
      serviceWorkerPath: '/firebase-messaging-sw.js',
      serviceWorkerScope: '/',
      iconPath: '/icons/push-icon-192.png',
      badgePath: '/icons/push-badge-72.png',
      softAsk: false,
      onClick: async ({ roomJID, messageId, url, data }) => {
        // Your app-level routing/analytics can live here.
        console.log('Push clicked:', { roomJID, messageId, url, data });
      },
    },
  }}
/>
  1. Optional: use hook directly for controlled permission flow:
import { usePushNotifications } from '@ethora/chat-component';

function PushPermissionButton() {
  const { requestPermission } = usePushNotifications({
    enabled: true,
    softAsk: true,
    vapidPublicKey: 'PLACEHOLDER_VAPID_PUBLIC_KEY',
  });

  return <button onClick={() => requestPermission()}>Enable Push</button>;
}

iconPath and badgePath should point to public, reachable assets (for example, files from your app public/ directory).

Auth Strategies

| Strategy | Config shape | Best for | | --- | --- | --- | | Default fallback (legacy quirk) | no auth block / defaultLogin | Legacy/demo flows; prefer explicit auth modes in production | | Injected user | userLogin: { enabled: true, user } | App already has authenticated user/session | | JWT login | jwtLogin: { enabled: true, token } | Token-based backend auth flow | | Google login | googleLogin: { enabled: true, firebaseConfig } | Google SSO using Firebase | | Custom login function | customLogin: { enabled: true, loginFunction } | Fully custom identity provider |

Example: injected user (bypass login screen)

<Chat
  config={{
    userLogin: {
      enabled: true,
      user: {
        _id: 'PLACEHOLDER_USER_ID',
        appId: 'PLACEHOLDER_APP_ID',
        walletAddress: 'PLACEHOLDER_WALLET_ADDRESS',
        defaultWallet: { walletAddress: 'PLACEHOLDER_WALLET_ADDRESS' },
        firstName: 'Jane',
        lastName: 'Doe',
        xmppPassword: 'PLACEHOLDER_XMPP_PASSWORD',
        token: 'PLACEHOLDER_ACCESS_TOKEN',
        refreshToken: 'PLACEHOLDER_REFRESH_TOKEN',
        username: 'PLACEHOLDER_USERNAME',
      },
    },
  }}
/>

Example: JWT login

<Chat
  config={{
    jwtLogin: {
      enabled: true,
      token: 'PLACEHOLDER_JWT_TOKEN',
    },
    refreshTokens: {
      enabled: true,
      refreshFunction: async () => {
        return {
          accessToken: 'PLACEHOLDER_NEW_ACCESS_TOKEN',
          refreshToken: 'PLACEHOLDER_NEW_REFRESH_TOKEN',
        };
      },
    },
  }}
/>

Hooks and API Exports

| Export | Type | Purpose | | --- | --- | --- | | Chat | React component | Main chat component. | | XmppProvider | React provider | Provides XMPP client context for internal hooks/state. | | useUnread | hook | Returns unread counters. | | logoutService | service | Programmatic logout utility. | | useQRCodeChat | hook | Handle QR-based room links. | | handleQRChatId | function | Parse/process QR chat ID from URL. | | useInAppNotifications | hook | Enables and handles in-app notifications. | | usePushNotifications | hook | Push subscription + foreground handling workflow. | | resendMessage | function | Retry sending failed/pending messages. |

Basic hook usage:

import { useUnread, logoutService } from '@ethora/chat-component';

function HeaderActions() {
  const { totalCount } = useUnread();

  return (
    <div>
      <span>Unread: {totalCount}</span>
      <button onClick={() => logoutService.performLogout()}>Logout</button>
    </div>
  );
}

logoutService.performLogout() behavior:

  • Dispatches chatSettingStore/logout
  • Dispatches rooms/setLogoutState
  • Dispatches roomHeap/clearHeap
  • Triggers logout middleware, which emits ethora-xmpp-logout
  • XmppProvider listens to that event and disconnects active XMPP client

Use Cases and Feature Coverage

| Area | Status in this package | | --- | --- | | One-room embedded chat | Available | | Multi-room chat UI | Available | | Message interactions (reply/copy/edit/delete/report/reactions) | Available | | Typing indicator | Available | | Profile interactions in chat | Available (can be disabled) | | File/media attachments | Available with ongoing enhancements | | In-app notifications | Available | | Web push notifications | Available | | Wallet/assets and extended social modules | Primarily in full Ethora platform |

Hosted vs Self-Host Guidance

| Model | Best for | Pros | Tradeoffs | | --- | --- | --- | --- | | Hosted Ethora backend | Fast time-to-market, smaller teams, MVPs | Fast setup, managed backend operations, easier push/auth onboarding | Less infrastructure-level control | | Self-hosted Ethora stack | Regulated environments, deep infra control | Full control over infrastructure, compliance customization, internal network deployment options | Higher DevOps/maintenance overhead | | Hybrid | Gradual migration or split workloads | Can start fast and migrate critical paths later | More architecture complexity |

Feature Roadmap

This is a practical planning snapshot for cross-platform consumers. It is not a release commitment.

| Surface | Current state | Notes | | --- | --- | --- | | Web React (@ethora/chat-component) | Available now | This repository. | | React Native | Via broader Ethora stack | Track platform-specific implementation in Ethora repos/docs. | | Swift (iOS native) | Planned / ecosystem-level | Confirm status with Ethora team for production timelines. | | Kotlin (Android native) | Planned / ecosystem-level | Confirm status with Ethora team for production timelines. | | Flutter | Planned / ecosystem-level | Confirm status with Ethora team for production timelines. | | Additional roadmap items | Ongoing | Media improvements, richer profile/wallet experiences, broader integration guides. |

Troubleshooting

| Issue | Likely cause | Fix | | --- | --- | --- | | useXmppClient must be used within an XmppProvider | Using internal XMPP-dependent logic without provider context | Wrap the app tree with XmppProvider where needed. | | Chat loads but no rooms appear | Auth/app context mismatch or room fetching restrictions | Verify appId, user credentials/tokens, baseUrl, and customRooms settings. | | Push permission never appears | softAsk: true without manual trigger, insecure origin, or missing VAPID key | Trigger requestPermission(), use HTTPS/localhost, set valid VAPID key. | | Service worker not found | firebase-messaging-sw.js missing in public dir | Run npx @ethora/chat-component ethora-chat or copy file manually. | | Login loop / auth failure | Wrong token/user object shape | Validate jwtLogin, userLogin.user, and refresh token flow. |

Testing

Same two-layer split as the Android and iOS SDKs.

Layer 1 — Vitest + React Testing Library (this repo)

Hermetic component tests, no real server, no XMPP. Run with:

npm test          # one-shot
npm run test:watch # watch mode

| Where | What | Run with | |-------|------|----------| | src/**/*.test.tsx | Component tests using @testing-library/react — render the component in isolation, drive it with userEvent, assert behavior | npm test | | src/test/setup.ts | Vitest global setup (jest-dom matchers, jsdom polyfills for matchMedia / IntersectionObserver / scrollIntoView) | (loaded automatically) | | src/test/renderWithProviders.tsx | Test render helper that wraps a component with a fresh Redux store + ToastProvider — no persisted-store / saga / XMPP middleware leaking between tests | (imported by tests) | | src/test/testIds.ts | Stable data-testid constants matching Android *TestTags and iOS *AccessibilityID, so a single Maestro flow exercises the same intent on either mobile platform | (imported by tests + components) |

Current Layer-1 coverage

| Component | Test | Asserts | |-----------|------|---------| | <Login /> | renders email + password fields and submit button | data-testid selectors all resolve | | <Login /> | shows email validation error for an invalid email | Bad email → error message + no API call fires | | <Login /> | shows password length error for short passwords | Password < 6 chars → error + no API call | | <Login /> | calls loginEmail with valid credentials | Valid creds → mocked loginEmail called once with the right args |

Gaps to cover in follow-up PRs (file an issue + a test in the same PR when you tackle one):

  • <Register /> — same shape as Login + Google sign-up branch
  • <MessageBubble /> — body / deleted / sendFailed / reaction states
  • <RoomList /> — search filter, active-room highlight, badge counts
  • <MessageInput /> (chat input) — send callback fires, edit mode, reply mode, disabled state
  • <URLPreviewCard /> — link extraction + image fallback

Layer 2 — End-to-end Playwright

The host app ethora-app-reactjs runs a Playwright suite. Its current scope is public-page smoke (login / register / 404). The chat-component flows themselves are not yet covered by Playwright — when they are, those tests should resolve nodes via the same data-testid values exported from src/test/testIds.ts here.

Cross-platform parity

| Identifier | This repo | Android | iOS | |------------|-----------|---------|-----| | chat_input | ChatInputTestIds.inputField | ChatInputTestTags.INPUT_FIELD | ChatInputAccessibilityID.inputField | | chat_send_button | ChatInputTestIds.sendButton | ChatInputTestTags.SEND_BUTTON | ChatInputAccessibilityID.sendButton | | chat_attach_button | ChatInputTestIds.attachButton | ChatInputTestTags.ATTACH_BUTTON | ChatInputAccessibilityID.attachButton | | chat_message_image | MessageBubbleTestIds.mediaContent | MessageBubbleTestTags.MEDIA_CONTENT | MessageBubbleAccessibilityID.mediaContent | | rooms_list | RoomListTestIds.roomsList | RoomListViewTestTags.ROOMS_LIST | RoomListAccessibilityID.roomsList | | room_row | RoomListTestIds.roomRow | RoomListViewTestTags.ROOM_ROW | RoomListAccessibilityID.roomRow | | auth_email_input | AuthTestIds.emailInput | (web-only) | (web-only) | | auth_submit_button | AuthTestIds.submitButton | (web-only) | (web-only) |

Adding a test for a fix or new feature

  • Behavior bug in a chat component → add a Vitest test in this repo, in the same PR as the fix.
  • Integration bug (something a host app sees but a hermetic test can't) → add a Playwright test in ethora-app-reactjs, in a paired PR.
  • Cross-platform parity gap → make sure the matching Android Compose test or Maestro flow exists too.

Ethora Links and Support

Product

  • Website: https://ethora.com/
  • Try Ethora: https://app.chat.ethora.com/register
  • SDK Playground (live): https://playground.chat.ethora.com
  • Platform status / uptime: https://uptime.chat.ethora.com

Developer Docs

  • Chat component docs: https://docs.ethora.com/
  • API docs (Swagger, live): https://api.chat.ethora.com/api-docs/#/
  • Ethora GitHub hub: https://github.com/dappros/ethora
  • This package repo: https://github.com/dappros/ethora-chat-component
  • Ethora GitHub organization: https://github.com/dappros

Community and Support

  • Forum: https://forum.ethora.com/
  • Discord: https://discord.gg/Sm6bAHA3ZC
  • Contact: https://ethora.com/#contact

License

AGPL. See LICENSE.txt.