Files
openhuman/docs/src/07-providers.md
T
Steven EnamakelandGitHub 58969667d9 fix: add ESLint and Prettier configuration (#15)
* ran prettier

* Refactor ESLint configuration to use ES module syntax and enhance TypeScript support

- Converted CommonJS `require` statements to ES module `import` syntax for better compatibility with modern JavaScript.
- Added new paths to ignore in ESLint configuration to exclude additional directories.
- Updated TypeScript file patterns to be more specific, improving linting accuracy.
- Adjusted React hooks rules to allow certain patterns, enhancing flexibility in component design.

These changes improve the maintainability and clarity of the ESLint configuration, aligning it with current best practices.

* Refactor ESLint configuration to use ES module syntax and enhance TypeScript support

- Converted CommonJS `require` statements to ES module `import` syntax for better compatibility with modern JavaScript.
- Added new paths to ignore in ESLint configuration to exclude additional directories.
- Updated TypeScript file patterns to be more specific, improving linting accuracy.
- Introduced new React hooks rules and adjusted existing rules for better adherence to best practices.
- Made minor adjustments to import statements across various files for consistency and clarity.

These changes improve the overall linting setup and ensure better code quality across the project.

* Refactor import statements across multiple files for consistency

- Updated import statements to use TypeScript's `type` syntax for type imports, enhancing clarity and consistency across the codebase.
- Consolidated imports from the same module into single statements, improving readability and maintainability.

These changes streamline the code structure and align with best practices for TypeScript imports.

* Refactor import statements in memory manager for improved clarity

- Updated import statements to consolidate type imports and enhance readability.
- Removed redundant imports, streamlining the code structure in the memory manager file.

These changes align with best practices for TypeScript imports and improve maintainability.

* Add Husky for pre-commit and pre-push hooks

- Introduced Husky to manage Git hooks, enhancing the development workflow.
- Added pre-commit and pre-push scripts to enforce code formatting and linting checks before commits and pushes.
- Updated package.json to include Husky as a dependency and added a prepare script for setup.

These changes improve code quality and ensure adherence to formatting and linting standards during the development process.

* Refactor import statements for improved clarity and consistency

- Updated import statements across multiple files to consolidate type imports and enhance readability.
- Adjusted the order of imports for better organization and alignment with best practices in TypeScript.

These changes streamline the code structure and improve maintainability throughout the project.

* ran formatter

* Refactor import statements and improve code formatting across multiple files

- Consolidated and reordered import statements for better clarity and consistency in `SkillsGrid.tsx`, `SkillProvider.tsx`, and `index.ts`.
- Enhanced readability by adjusting formatting and removing redundant lines.
- These changes align with best practices for TypeScript imports and improve overall maintainability of the codebase.

* Refactor and optimize code in multiple components

- Removed redundant properties from the `STATUS_DISPLAY` object in `SkillsGrid.tsx` to streamline status handling.
- Consolidated import statements in `SettingsModal.tsx` for improved organization.
- Simplified state management and error handling in `BillingPanel.tsx`, enhancing performance and readability.
- Added `REHYDRATE` import to `index.ts` for better state persistence management.

These changes improve code clarity, maintainability, and align with best practices in TypeScript development.

* Consolidate import statements in SettingsModal.tsx for improved organization

* Add Prettier and ESLint checks to typecheck workflow

- Integrated a Prettier formatting check to ensure code style consistency.
- Added an ESLint step to enforce code quality and catch potential issues.
- These enhancements improve the development workflow by automating formatting and linting checks during the typecheck process.

* Add activeSkillDescription state to ConnectionsPanel and ConnectStep

- Introduced activeSkillDescription state in both ConnectionsPanel and ConnectStep components to store and manage skill descriptions.
- Updated the SkillSetupModal to accept skillDescription as a prop, enhancing the modal's functionality and data handling.

These changes improve the user experience by providing more detailed information about skills during the connection setup process.

* Enhance pre-push hook to include TypeScript compile check

- Added a TypeScript compile check to the pre-push script, ensuring that code compiles successfully before pushing.
- Updated error handling to include compile errors alongside formatting and linting issues, providing clearer feedback to developers.

These changes improve the reliability of the codebase by preventing non-compiling code from being pushed.

* Update GitHub workflows for pull request handling and publishing logic

- Modified the package-and-publish workflow to support pull request events, ensuring proper handling of branches.
- Adjusted the SHOULD_PUBLISH environment variable to differentiate between pull requests and main branch events.
- Updated the pr-protection workflow to focus solely on the main branch, removing references to the master branch.

These changes enhance the CI/CD process by refining branch handling and improving clarity in workflow conditions.
2026-02-02 06:24:50 +05:30

9.6 KiB

Providers

React context providers manage service lifecycle and provide shared state.

Provider Chain

The providers wrap the application in a specific order:

// App.tsx
<Provider store={store}>
  <PersistGate loading={null} persistor={persistor}>
    <UserProvider>
      <SocketProvider>
        <TelegramProvider>
          <HashRouter>
            <AppRoutes />
          </HashRouter>
        </TelegramProvider>
      </SocketProvider>
    </UserProvider>
  </PersistGate>
</Provider>

Order matters because:

  1. Redux must be outermost for state access
  2. PersistGate rehydrates state before rendering children
  3. SocketProvider depends on Redux auth token
  4. TelegramProvider depends on Redux telegram state
  5. HashRouter provides navigation to all routes

SocketProvider (providers/SocketProvider.tsx)

Manages Socket.io connection lifecycle and MCP initialization.

Responsibilities

  • Auto-connect when auth token is available
  • Auto-disconnect when token is cleared
  • Initialize MCP server when socket connects
  • Update Redux with connection status

Implementation

interface SocketContextValue {
  socket: Socket | null;
  isConnected: boolean;
  emit: (event: string, data: unknown) => void;
  on: (event: string, handler: Function) => void;
  off: (event: string, handler: Function) => void;
}

export function SocketProvider({ children }) {
  const token = useAppSelector((state) => state.auth.token);
  const userId = useAppSelector((state) => state.user.profile?.id);
  const dispatch = useAppDispatch();

  useEffect(() => {
    if (!token || !userId) {
      socketService.disconnect();
      dispatch(setSocketStatus({ userId, status: 'disconnected' }));
      return;
    }

    // Connect with auth token
    socketService.connect(token);
    dispatch(setSocketStatus({ userId, status: 'connecting' }));

    // Handle connection events
    socketService.on('connect', () => {
      dispatch(setSocketStatus({ userId, status: 'connected' }));
      dispatch(setSocketId({ userId, socketId: socketService.getSocket()?.id }));

      // Initialize MCP server
      initMCPServer(socketService.getSocket());
    });

    socketService.on('disconnect', () => {
      dispatch(setSocketStatus({ userId, status: 'disconnected' }));
      cleanupMCP();
    });

    socketService.on('connect_error', (error) => {
      console.error('Socket connection error:', error);
      dispatch(setSocketStatus({ userId, status: 'disconnected' }));
    });

    return () => {
      socketService.disconnect();
      cleanupMCP();
    };
  }, [token, userId]);

  const contextValue: SocketContextValue = {
    socket: socketService.getSocket(),
    isConnected: socketService.isConnected(),
    emit: socketService.emit.bind(socketService),
    on: socketService.on.bind(socketService),
    off: socketService.off.bind(socketService)
  };

  return (
    <SocketContext.Provider value={contextValue}>
      {children}
    </SocketContext.Provider>
  );
}

Usage

import { useSocket } from '../providers/SocketProvider';

function MyComponent() {
  const { socket, isConnected, emit, on, off } = useSocket();

  useEffect(() => {
    const handler = (data) => console.log('Received:', data);
    on('event-name', handler);
    return () => off('event-name', handler);
  }, [on, off]);

  const sendMessage = () => {
    emit('send-message', { text: 'Hello!' });
  };

  return (
    <div>
      <span>Status: {isConnected ? 'Connected' : 'Disconnected'}</span>
      <button onClick={sendMessage}>Send</button>
    </div>
  );
}

TelegramProvider (providers/TelegramProvider.tsx)

Manages Telegram MTProto connection lifecycle.

Responsibilities

  • Initialize MTProto client when user is authenticated
  • Connect to Telegram servers
  • Store session string in Redux
  • Provide Telegram context to children

Implementation

interface TelegramContextValue {
  client: TelegramClient | null;
  connectionStatus: ConnectionStatus;
  authStatus: AuthStatus;
  connect: () => Promise<void>;
  disconnect: () => Promise<void>;
}

export function TelegramProvider({ children }) {
  const dispatch = useAppDispatch();
  const userId = useAppSelector((state) => state.user.profile?.id);
  const telegramState = useAppSelector((state) =>
    state.telegram.byUser[userId]
  );

  useEffect(() => {
    if (!userId) return;

    // Parallel initialization for faster startup
    const init = async () => {
      try {
        // Initialize and connect in parallel
        await Promise.all([
          dispatch(initializeTelegram(userId)).unwrap(),
          dispatch(connectTelegram(userId)).unwrap()
        ]);
      } catch (error) {
        console.error('Telegram initialization failed:', error);
      }
    };

    init();

    return () => {
      dispatch(disconnectTelegram(userId));
    };
  }, [userId]);

  // Restore session from persisted state
  useEffect(() => {
    if (telegramState?.sessionString) {
      const client = mtprotoService.getInstance().getClient();
      if (client) {
        client.setSession(telegramState.sessionString);
      }
    }
  }, [telegramState?.sessionString]);

  const contextValue: TelegramContextValue = {
    client: mtprotoService.getInstance().getClient(),
    connectionStatus: telegramState?.connectionStatus || 'disconnected',
    authStatus: telegramState?.authStatus || 'not_authenticated',
    connect: () => dispatch(connectTelegram(userId)).unwrap(),
    disconnect: () => dispatch(disconnectTelegram(userId)).unwrap()
  };

  return (
    <TelegramContext.Provider value={contextValue}>
      {children}
    </TelegramContext.Provider>
  );
}

Usage

import { useTelegram } from '../providers/TelegramProvider';

function ChatList() {
  const { client, connectionStatus, authStatus } = useTelegram();
  const [chats, setChats] = useState([]);

  useEffect(() => {
    if (connectionStatus === 'connected' && authStatus === 'authenticated') {
      const fetchChats = async () => {
        const dialogs = await client.getDialogs({ limit: 20 });
        setChats(dialogs);
      };
      fetchChats();
    }
  }, [client, connectionStatus, authStatus]);

  if (connectionStatus !== 'connected') {
    return <div>Connecting to Telegram...</div>;
  }

  return (
    <ul>
      {chats.map((chat) => (
        <li key={chat.id}>{chat.title}</li>
      ))}
    </ul>
  );
}

UserProvider (providers/UserProvider.tsx)

Minimal user context provider (most user state is in Redux).

Responsibilities

  • Legacy user context for compatibility
  • May be deprecated in favor of Redux

Implementation

interface UserContextValue {
  user: User | null;
  loading: boolean;
}

export function UserProvider({ children }) {
  const user = useAppSelector((state) => state.user.profile);
  const loading = useAppSelector((state) => state.user.loading);

  return (
    <UserContext.Provider value={{ user, loading }}>
      {children}
    </UserContext.Provider>
  );
}

Usage

import { useUserContext } from '../providers/UserProvider';

function Header() {
  const { user, loading } = useUserContext();

  if (loading) return <Skeleton />;
  if (!user) return null;

  return <span>Welcome, {user.firstName}</span>;
}

Provider Patterns

Effect-Based Lifecycle

Providers use useEffect to manage service lifecycle:

useEffect(() => {
  // Setup on mount or dependency change
  service.connect();

  // Cleanup on unmount or dependency change
  return () => {
    service.disconnect();
  };
}, [dependencies]);

Redux Integration

Providers read from and dispatch to Redux:

// Read state
const token = useAppSelector(state => state.auth.token);

// Dispatch actions
const dispatch = useAppDispatch();
dispatch(setStatus({ userId, status: 'connected' }));

Parallel Initialization

TelegramProvider runs init and connect in parallel:

await Promise.all([
  dispatch(initializeTelegram(userId)).unwrap(),
  dispatch(connectTelegram(userId)).unwrap(),
]);

This reduces startup time compared to sequential operations.

Session Restoration

Providers restore persisted state on mount:

useEffect(() => {
  if (persistedSession) {
    service.restoreSession(persistedSession);
  }
}, [persistedSession]);

Context vs Redux

Use Context For Use Redux For
Service instances (socket, client) Serializable state (status, data)
Methods (emit, on, off) Persisted state (sessions, tokens)
Derived values Complex state logic

Example:

  • SocketContext provides socket instance and emit method
  • Redux stores socketStatus and socketId

Testing Providers

Mock Provider for Tests

// test-utils.tsx
const mockSocketContext: SocketContextValue = {
  socket: null,
  isConnected: true,
  emit: jest.fn(),
  on: jest.fn(),
  off: jest.fn()
};

export function TestProviders({ children }) {
  return (
    <Provider store={testStore}>
      <SocketContext.Provider value={mockSocketContext}>
        {children}
      </SocketContext.Provider>
    </Provider>
  );
}

Testing Provider Effects

test('SocketProvider connects when token is available', () => {
  const store = createTestStore({ auth: { token: 'test-token' } });

  render(
    <Provider store={store}>
      <SocketProvider>
        <TestComponent />
      </SocketProvider>
    </Provider>
  );

  expect(socketService.connect).toHaveBeenCalledWith('test-token');
});

Previous: Components | Next: Hooks & Utils