# @iblai/iblai-js

Unified JavaScript/TypeScript SDK for building applications on the [IBL.ai](https://ibl.ai) platform. This package bundles the core IBL frontend libraries under a single namespace with convenient subpath imports for data fetching, UI components, authentication, and utilities.

## Installation

```bash
npm install @iblai/iblai-js
# or
pnpm add @iblai/iblai-js
# or
yarn add @iblai/iblai-js
```

### Peer Dependencies

The following peer dependencies must be installed in your project:

```bash
npm install react react-dom @reduxjs/toolkit react-redux @iblai/iblai-api
```

| Peer Dependency      | Version     |
| -------------------- | ----------- |
| `react`              | >= 19.1.0   |
| `react-dom`          | >= 19.1.0   |
| `@reduxjs/toolkit`   | >= 2.7.0    |
| `react-redux`        | >= 9.2.0    |
| `@iblai/iblai-api`   | >= 4.166.0-ai |

`next` (>= 15) is an **optional** peer dependency, required only when using `@iblai/iblai-js/web-containers/next`.

`@playwright/test` and `@axe-core/playwright` are **optional** peer dependencies, required only when using `@iblai/iblai-js/playwright`.

## Framework Configuration

### Next.js

The SDK ships pre-compiled with `'use client'` directives already applied to client components. **Do not** add `@iblai/iblai-js` or its sub-packages (`@iblai/data-layer`, `@iblai/web-containers`, `@iblai/web-utils`) to `transpilePackages` in your `next.config.ts` — they are ready to use as-is.

```typescript
// next.config.ts
const nextConfig = {
  // Do NOT add @iblai packages here — they ship pre-built dist files
  // transpilePackages: ['@iblai/iblai-js'], // ← Don't do this
};
```

### Node.js 25+

If you're running Node.js 25 or later, you **must** disable the experimental Web Storage API in Node.js. Without this flag, Node.js provides a server-side `localStorage` that conflicts with the SDK's browser-only storage guards, causing hydration errors and runtime failures.

Add this to your `package.json` scripts:

```json
{
  "scripts": {
    "dev": "NODE_OPTIONS='--no-experimental-webstorage' next dev",
    "start": "NODE_OPTIONS='--no-experimental-webstorage' next start"
  }
}
```

### Vite / CRA / Other React Frameworks

No special configuration required. Install and import directly.

## Styling Setup (Tailwind CSS)

The SDK's React components use Tailwind CSS utility classes. To ensure these classes are included in your app's CSS output, you need two things in your global CSS file:

1. **Import the SDK's base styles** (CSS variables for the IBL design system)
2. **Add a `@source` directive** so your Tailwind instance scans the SDK's compiled components for class names

### Tailwind CSS v4 (recommended)

In your global CSS file (e.g., `app/globals.css`):

```css
@import 'tailwindcss';

/* SDK: custom base styles (CSS variables, no Tailwind utilities) */
@import '@iblai/iblai-js/web-containers/styles';

/* SDK: scan compiled components so Tailwind generates their utility classes */
@source "../node_modules/@iblai/iblai-js/dist/web-containers/source";
```

The `@source` directive tells your Tailwind instance to scan the SDK's compiled JavaScript for class names like `lg:grid-cols-2`, `md:flex`, `hidden`, etc. Without this, responsive and utility classes used only in SDK components won't be generated.

### Tailwind CSS v3

In your `tailwind.config.js`, add the SDK's compiled output to the `content` array:

```js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './node_modules/@iblai/iblai-js/dist/web-containers/source/**/*.js',
  ],
};
```

Then import the base styles in your CSS:

```css
@import '@iblai/iblai-js/web-containers/styles';
```

### Why this is needed

The SDK does **not** ship pre-built Tailwind utility CSS. Instead, it ships:

- **Base styles** (`styles.css`) — CSS variables and custom component classes that don't depend on Tailwind
- **Compiled component source** (`source/index.esm.js`) — the bundled JavaScript containing all Tailwind class name strings

Your app's single Tailwind instance scans both your own code and the SDK's compiled source, then generates one unified set of utility classes. This avoids CSS cascade conflicts that would occur if two separate Tailwind builds produced overlapping `@layer utilities` blocks.

## Quick Start

### 1. Initialize the data layer

Call `initializeDataLayer` once at app startup, before any API calls or store creation. It configures the base URLs and a storage adapter that the SDK uses for token persistence.

```typescript
import { initializeDataLayer } from '@iblai/iblai-js/data-layer';
import type { StorageService } from '@iblai/iblai-js/data-layer';

// Implement the StorageService interface for your environment.
// For web apps, wrap localStorage; for React Native, use AsyncStorage.
const storageService: StorageService = {
  getItem: async (key) => localStorage.getItem(key),
  setItem: async (key, value) => localStorage.setItem(key, value),
  removeItem: async (key) => localStorage.removeItem(key),
};

initializeDataLayer(
  'https://base.manager.iblai.app', // Platform manager URL (dmUrl)
  'https://learn.iblai.app',        // LMS URL (lmsUrl)
  storageService,
  // Optional: HTTP error handlers keyed by status code
  // { 401: () => redirectToLogin(), 403: () => showForbidden() }
);
```

### 2. Configure the Redux store

The SDK provides pre-bundled reducers and middleware for quick store setup. Choose the bundle that matches your app:

```typescript
import { configureStore } from '@reduxjs/toolkit';
import { mentorReducer, mentorMiddleware } from '@iblai/iblai-js/data-layer';
// For Skills apps, use skillsReducer and skillsMiddleware instead

const store = configureStore({
  reducer: mentorReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(mentorMiddleware),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
```

### 3. Wrap your app with providers

```tsx
import { Provider } from 'react-redux';
import { AuthProvider, MentorProvider } from '@iblai/iblai-js/web-utils';

function App({ children }) {
  return (
    <Provider store={store}>
      <AuthProvider>
        <MentorProvider>
          {children}
        </MentorProvider>
      </AuthProvider>
    </Provider>
  );
}
```

### 4. Use hooks and components

```tsx
import { useChatV2 } from '@iblai/iblai-js/web-utils';
import { Loader } from '@iblai/iblai-js/web-containers';

function ChatPage({ mentorSlug }: { mentorSlug: string }) {
  const { messages, sendMessage, isLoading } = useChatV2({ mentor: mentorSlug });

  if (isLoading) return <Loader />;

  return (
    <div>
      {messages.map((msg) => (
        <p key={msg.id}>{msg.content}</p>
      ))}
      <button onClick={() => sendMessage('Hello!')}>Send</button>
    </div>
  );
}
```

---

## Subpath Exports

### `@iblai/iblai-js/data-layer`

Redux-based data layer for all IBL.ai API interactions. Built on [RTK Query](https://redux-toolkit.js.org/rtk-query/overview).

**Key exports:**

| Export | Description |
| --- | --- |
| `initializeDataLayer(dmUrl, lmsUrl, storageService, httpErrorHandler?)` | One-time SDK initialization (must be called before API usage) |
| `mentorReducer` / `mentorMiddleware` | Pre-bundled Redux reducer and middleware for Mentor apps |
| `skillsReducer` / `skillsMiddleware` | Pre-bundled Redux reducer and middleware for Skills apps |
| `IblDataLayer` | Core singleton with storage and config access |
| `StorageService` | Interface for pluggable persistence (localStorage, AsyncStorage, etc.) |
| `STORAGE_KEYS` | Constants for token storage keys (`DM_TOKEN_KEY`, `AXD_TOKEN_KEY`, `EDX_TOKEN_KEY`) |
| `mentorApiSlice` | RTK Query slice for mentor CRUD operations |
| `chatApiSlice` | RTK Query slice for chat sessions and messages |
| `tenantApiSlice` | RTK Query slice for tenant management |
| `userApiSlice` | RTK Query slice for user profiles |
| `platformApiSlice` | RTK Query slice for platform configuration |
| `notificationsApiSlice` | RTK Query slice for notification management |

```typescript
import {
  initializeDataLayer,
  mentorReducer,
  mentorMiddleware,
  skillsReducer,
  skillsMiddleware,
  IblDataLayer,
  STORAGE_KEYS,
  mentorApiSlice,
  chatApiSlice,
} from '@iblai/iblai-js/data-layer';
import type { StorageService } from '@iblai/iblai-js/data-layer';
```

---

### `@iblai/iblai-js/web-containers`

Framework-agnostic React component library. These components work in any React app (Next.js, Vite, CRA, etc.).

**Key exports:**

| Export | Description |
| --- | --- |
| `Loader` | Loading spinner component |
| `TenantSwitch` | Tenant/organization switcher dropdown |
| `useIframeMessageHandler` | Hook for cross-iframe communication |
| `useLocalStorage` | Hook for reactive localStorage state |
| `useTimeTracking` | Hook for user activity time tracking |
| `useTauri` | Hook for Tauri desktop app integration |
| `isTauriApp` | Utility to detect Tauri runtime |

```typescript
import {
  Loader,
  TenantSwitch,
  useIframeMessageHandler,
  useLocalStorage,
} from '@iblai/iblai-js/web-containers';
```

---

### `@iblai/iblai-js/web-containers/next`

Next.js-specific components and hooks. **Requires `next` as a dependency.**

**Key exports:**

| Export | Description |
| --- | --- |
| `SsoLogin` | Full SSO login page component |
| `initializeLocalStorageWithObject` | Populate localStorage from SSO data during login |
| `ErrorPage` | Server-side error page |
| `ClientErrorPage` | Client-side error boundary page |
| `UserProfileDropdown` | User avatar/menu dropdown |
| `UserProfileModal` | Full user profile editor modal |
| `Account` | Account settings component |
| `OrganizationTab` | Organization management tab |
| `useTimeTracker` | Hook for page-level time tracking (uses `next/navigation`) |
| `useGetChatDetails` | Hook for fetching chat session details |

```typescript
import {
  SsoLogin,
  ErrorPage,
  UserProfileDropdown,
  useTimeTracker,
  useGetChatDetails,
} from '@iblai/iblai-js/web-containers/next';
```

---

### `@iblai/iblai-js/web-containers/sso`

Framework-agnostic SSO utilities for handling authentication callbacks. Use these to build custom SSO login flows in any React framework.

**Key exports:**

| Export | Description |
| --- | --- |
| `handleSsoCallback(options?)` | Complete SSO callback handler — parses URL params, stores tokens, syncs cookies, and redirects |
| `parseSsoData(queryParam)` | Decode and parse the base64-encoded SSO data from the callback URL |
| `initializeLocalStorageWithObject(data, keys)` | Store parsed SSO data into localStorage using configurable keys |
| `syncSsoDataToCookies(data)` | Sync authentication data to cookies for cross-subdomain sharing |
| `setCookie(name, value, days?)` | Set a cookie with configurable expiry |
| `getBaseDomain()` | Extract the base domain for cookie sharing (e.g., `example.com` from `app.example.com`) |
| `DEFAULT_SSO_STORAGE_KEYS` | Default localStorage key names (`CURRENT_TENANT`, `USER_DATA`, `TENANTS`) |

```typescript
import {
  handleSsoCallback,
  parseSsoData,
  initializeLocalStorageWithObject,
  DEFAULT_SSO_STORAGE_KEYS,
} from '@iblai/iblai-js/web-containers/sso';
import type { SsoStorageKeys, HandleSsoCallbackOptions } from '@iblai/iblai-js/web-containers/sso';
```

**Example — handling an SSO callback:**

```typescript
// In your SSO callback route (e.g., /sso/callback)
import { handleSsoCallback } from '@iblai/iblai-js/web-containers/sso';

// This parses the URL, stores tokens, syncs cookies, and redirects:
await handleSsoCallback({
  redirectUrl: '/dashboard',  // Where to go after login
});
```

---

### `@iblai/iblai-js/web-utils`

Authentication, state management, chat hooks, and general-purpose utilities.

**Key exports:**

| Export | Description |
| --- | --- |
| `AuthProvider` | React context provider for authentication state |
| `MentorProvider` | React context provider for active mentor state |
| `TenantProvider` | React context provider for tenant/organization state |
| `useChatV2` | Primary hook for chat interactions (send messages, stream responses) |
| `useAdvancedChat` | Extended chat hook with tool use, file attachments, and advanced features |
| `useSubscriptionHandler` | Hook for managing Stripe subscription flows |
| `isLoggedIn()` | Check if the current user has a valid session |
| `redirectToAuthSpa(options?)` | Redirect to the IBL auth SPA for login |
| `handleLogout()` | Clear tokens and redirect to logout |
| `WithPermissions` | HOC for role-based access control (RBAC) |
| `checkRbacPermission(permission, userPermissions)` | Programmatic RBAC permission check |
| `chatSlice` | Redux slice for chat UI state (selected mentor, session, etc.) |

```typescript
import {
  AuthProvider,
  MentorProvider,
  TenantProvider,
  useChatV2,
  useAdvancedChat,
  isLoggedIn,
  redirectToAuthSpa,
  handleLogout,
  WithPermissions,
  checkRbacPermission,
  chatSlice,
} from '@iblai/iblai-js/web-utils';
```

---

### `@iblai/iblai-js/playwright`

Shared Playwright test utilities for E2E testing across IBL.ai apps. Unlike the other subpaths, this is **not** a re-export — it contains original source code for test infrastructure shared between standalone app repos (mentorai, skillsai).

**Requires:** `@playwright/test` and `@axe-core/playwright` as dev dependencies.

```bash
pnpm add -D @playwright/test @axe-core/playwright dotenv cross-env
```

#### Config Generation

Generate a complete Playwright config with per-browser auth setup projects:

```typescript
// e2e/playwright.config.ts
import { createPlaywrightConfig } from '@iblai/iblai-js/playwright';
import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.resolve(__dirname, '.env.development') });

export default createPlaywrightConfig({
  platforms: [
    {
      name: 'skills',
      dependencies: ['setup'],
      otherTestMatch: ['**skills/*/*.spec.ts'],
    },
  ],
});
```

`createPlaywrightConfig` generates browser-specific setup projects (Chrome, Firefox, Safari, Edge) that each run `auth.setup.ts`, then creates test projects for each platform/browser combination. The `devices` option defaults to `['Desktop Chrome', 'Desktop Firefox', 'Desktop Safari', 'Desktop Edge']`.

#### Auth Setup

Create an `auth.setup.ts` that authenticates once per browser and saves the storage state:

```typescript
// e2e/auth.setup.ts
import { test as setup, createAuthSetup } from '@iblai/iblai-js/playwright';

setup(
  'authenticate',
  createAuthSetup({
    hostUrl: process.env.SKILLS_HOST || '',
    authHost: process.env.AUTH_HOST || '',
    appName: 'skills',
    postLoginUrlMatcher: (url) =>
      url.href.includes('/home') || url.href.includes('/start'),
  })
);
```

`createAuthSetup` supports four auth flows via the `authFlow` option: `'username_password'` (default), `'magic_link'`, `'sso'`, and `'direct_sso'`. Credentials are read from `PLAYWRIGHT_USERNAME` and `PLAYWRIGHT_PASSWORD` env vars.

#### Custom Reporter

Re-export the SDK's custom reporter for Playwright:

```typescript
// e2e/custom-reporter.ts
export { CustomReporter as default } from '@iblai/iblai-js/playwright';
```

#### Test Helpers

```typescript
import {
  // Extended test fixture with step support (optional — most tests use @playwright/test directly)
  test,
  expect,

  // Navigation
  safeWaitForURL,
  waitForPageLoad,
  isFirefox,

  // Page interaction
  waitForPageReady,
  checkAdminStatus,
  reliableClick,
  reliableFill,
  waitForDialogReady,
  selectDateFromCalendar,

  // Accessibility
  expectNoAccessibilityViolations,
  expectNoAccessibilityViolationsOnDialogs,

  // Auth helpers
  loginWithEmailAndPassword,
  signUpWithEmailAndPassword,
  loginWithMicrosoftIdp,
  AuthFlowBuilder,
  createAuthSetup,

  // Logging
  logger,

  // Email testing
  MailsacClient,

  // Config generation
  createPlaywrightConfig,
  createEnvConfig,
  CustomReporter,
} from '@iblai/iblai-js/playwright';
```

#### Environment Variables

Tests expect these env vars (set them in `.env.development` or CI):

| Variable | Description |
| --- | --- |
| `SKILLS_HOST` / `MENTOR_NEXTJS_HOST` | App host URL |
| `AUTH_HOST` | Auth service URL |
| `PLAYWRIGHT_USERNAME` | Login username |
| `PLAYWRIGHT_PASSWORD` | Login password |
| `AUTH_FLOW` | Auth flow type (`username_password`, `magic_link`, `sso`, `direct_sso`) |
| `AUTH_IDP` | SSO identity provider name (required for `sso`/`direct_sso` flows) |

#### Directory Structure

Both mentorai and skillsai follow this layout:

```
e2e/
├── playwright.config.ts     # Uses createPlaywrightConfig()
├── auth.setup.ts            # Uses createAuthSetup()
├── custom-reporter.ts       # Re-exports CustomReporter
├── .env.development         # Environment variables
├── tests/
│   ├── utils.ts             # App-specific env var exports
│   ├── helpers.ts           # App-specific auth/login helpers
│   ├── shared.ts            # Shared test helper functions
│   └── <platform>/          # Test spec files by feature
└── utils/                   # App-specific helper modules
```

---

## Relationship to Standalone Packages

`@iblai/iblai-js` is a convenience wrapper. Each subpath is a direct re-export from its standalone package:

| Subpath Import                         | Source Package               |
| -------------------------------------- | ---------------------------- |
| `@iblai/iblai-js/data-layer`          | `@iblai/data-layer`         |
| `@iblai/iblai-js/web-containers`      | `@iblai/web-containers`     |
| `@iblai/iblai-js/web-containers/next` | `@iblai/web-containers/next`|
| `@iblai/iblai-js/web-containers/sso`  | `@iblai/web-containers/sso` |
| `@iblai/iblai-js/web-utils`           | `@iblai/web-utils`          |
| `@iblai/iblai-js/playwright`          | Original source (not a re-export) |

You can use the standalone packages directly if you only need one module. This unified package is useful when you need multiple IBL libraries and prefer a single dependency. The `playwright` subpath is unique — it contains original source code rather than re-exporting a standalone package.

## TypeScript

All subpath exports include TypeScript declarations. Types are co-located with their runtime exports:

```typescript
import type { StorageService } from '@iblai/iblai-js/data-layer';
import type { SsoStorageKeys, HandleSsoCallbackOptions } from '@iblai/iblai-js/web-containers/sso';
import type { UserProfileDropdownProps, UserMetadata } from '@iblai/iblai-js/web-containers/next';
```

## Build

```bash
pnpm build
```

Produces CommonJS, ESM, and TypeScript declaration files under `dist/`.

## Publishing (Maintainers)

This package uses `workspace:*` references for its dependencies on `@iblai/data-layer`, `@iblai/web-containers`, and `@iblai/web-utils`. These are automatically converted to real version numbers during publish.

**You must use `pnpm publish` (not `npm publish`) to publish this package.** Only `pnpm publish` converts `workspace:` references to actual version numbers. Using `npm publish` will publish with raw `workspace:*` strings, which breaks installation for all external consumers.

```bash
# Correct — converts workspace:* to resolved versions
pnpm publish --no-git-checks

# Wrong — leaves workspace:* in published package.json
npm publish  # ← Never use this for workspace packages
```

Publish the underlying packages first (in dependency order), then this package:

1. `@iblai/data-layer` — no internal deps
2. `@iblai/web-utils` — depends on data-layer
3. `@iblai/web-containers` — depends on data-layer and web-utils
4. `@iblai/iblai-js` — depends on all three

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development guide, including:

- How to make changes to the underlying packages (data-layer, web-utils, web-containers)
- How to test locally with `pnpm link` against consumer apps like skillsAI and mentorAI
- How the CI publishing pipeline works (tag-based for iblai-js, changeset-based for underlying packages)
- Troubleshooting common issues

## License

ISC
