npm package: @landbot/core
Description: Landbot client core
Latest version: 4.1.2

# @landbot/core

> Landbot client core library. Connects to the messaging database and Landbot app servers.

[![npm version](https://img.shields.io/npm/v/@landbot/core.svg)](https://www.npmjs.com/package/@landbot/core)

Modern ES Module library built with TypeScript and Vite for building Landbot chat integrations.

## ✨ Features

- 🎯 **ES Module only** - Modern, tree-shakeable
- 📦 **TypeScript support** - Full type definitions included
- 🔥 **Firebase integration** - Real-time message synchronization
- 📡 **RxJS pipelines** - Reactive message streams

## 📦 Installation

```bash
npm install @landbot/core
```

## 🚀 Quick Start

### Basic Usage

```javascript
import { Core } from '@landbot/core';

// Fetch configuration from your chatbot share url
const config = await fetch('https://landbot.online/v3/H-3235078-2DQVIGPPXBU1AF2X/index.html').then((res) => res.json());

const core = new Core(config);

// Initialize and get previous messages
const { messages } = await core.init();
console.log('Previous messages:', messages);

// Send a message
core.sendMessage({ type: 'text', message: 'Hey!' });
```

### TypeScript

```typescript
import { type ConfigProperties, Core, type Message } from '@landbot/core';

// Fetch configuration from your chatbot share url
const config = (await fetch('https://landbot.online/v3/H-3235078-2DQVIGPPXBU1AF2X/index.html').then((res) =>
  res.json(),
)) as ConfigProperties;

const core = new Core(config);

core.pipelines.$readableSequence.subscribe((message: Message) => {
  console.log(`[${message.type}]`, message.rich_text ?? message.title ?? message.message);
});

await core.init();
```

## 📡 Pipelines

Pipelines provide ordered, sequential message flows using RxJS Observables. This solves the problem of unordered messages from external services.

### `$sequence`

Basic sequential flow of messages:

```javascript
core.pipelines.$sequence.subscribe((message) => {
  console.log(`[${message.type}]`, message.rich_text ?? message.title ?? message.message);
});
```

### `$readableSequence` (Recommended)

Sequential flow with delays between messages for better readability:

```javascript
core.pipelines.$readableSequence.subscribe((message) => {
  console.log(`[${message.type}]`, message.rich_text ?? message.title ?? message.message);
  // Messages arrive with natural delays
});
```

### `$typingSequence`

Sequential flow with typing indicators:

```javascript
core.pipelines.$typingSequence.subscribe((data) => {
  if (data?.state) {
    console.log('Bot is typing...');
  } else if (data?.message) {
    console.log('Message:', data.message);
  }
});
```

## 🎯 API Reference

### Core Class

```typescript
class Core {
  constructor(config: ConfigProperties);

  // Properties
  config: Config & ConfigProperties;
  events: EventEmitter;
  pipelines: Pipelines;
  storage: Storage;
  client: Client;
  database: Database | null;

  // Methods
  init(): Promise<InitializationData>;
  destroy(): void;
  sendMessage(data: SendingMessage): Promise<void>;
  setConfig(config: Partial<ConfigProperties>): void;
}
```

### Methods

#### `init(): Promise<InitializationData>`

Initialize the core and retrieve conversation history.

```javascript
const data = await core.init();
console.log(data.messages); // Previous messages
console.log(data.landbotToken); // Session token retrieved from local storage, if enabled
console.log(data.isNewSession); // Whether this is a new session
```

**Returns:**

```typescript
interface InitializationData {
  landbotToken: string | null;
  isNewSession: boolean;
  messages: Record<string, Message>;
}
```

#### `sendMessage(data: SendingMessage): Promise<void>`

Send a message to the conversation.

```javascript
// Text message
core.sendMessage({
  type: 'text',
  message: 'Hello!',
});

// With custom data and payload
core.sendMessage({
  type: 'button',
  message: 'Option 1',
  payload: '$0',
  custom_data: { user_field: '123' },
});
```

**Parameters:**

```typescript
interface SendingMessage {
  type: MessageTypes;
  message: string;
  payload?: string;
  custom_data?: Record<string, any>;
  ui_key?: string;
}
```

#### `destroy(): void`

Clean up and destroy the core instance.

```javascript
core.destroy();
```

#### `setConfig(config: Partial<ConfigProperties>): void`

Update configuration (must be called before `init()`).

### Pipelines

Pipelines are RxJS Observables that provide ordered message flows:

```typescript
interface Pipelines {
  $sequence: Subject<Message>;
  $typingSequence: BehaviorSubject<TypingMessage | null>;
  $readableSequence: Subject<Message>;
}
```

#### `$sequence`

Basic sequential flow of messages:

```javascript
core.pipelines.$sequence.subscribe((message: Message) => {
  console.log(message);
});
```

#### `$readableSequence` (Recommended)

Sequential flow with natural delays between messages:

```javascript
core.pipelines.$readableSequence.subscribe((message: Message) => {
  console.log(`[${message.type}]`, message.message || message.title);
});
```

#### `$typingSequence`

Sequential flow with typing indicators:

```javascript
core.pipelines.$typingSequence.subscribe((data: TypingMessage | null) => {
  if (data?.state) {
    console.log('Bot is typing...');
  } else if (data?.message) {
    console.log('Message:', data.message);
  }
});
```

**Type:**

```typescript
interface TypingMessage {
  key: string;
  state: boolean;
  message: Message;
  delay?: number;
}
```

### Events

The core uses EventEmitter3 for event handling:

#### `new_message`

Triggered when a new message is received. **Note:** Prefer using pipelines for ordered message flow.

```javascript
core.events.on('new_message', (message: Message) => {
  console.log('New message:', message);
});
```

#### `init`

Triggered when initialization is complete:

```javascript
core.events.on('init', () => {
  console.log('Core initialized');
});
```

### Type Definitions

#### Message

```typescript
interface Message {
  id: string;
  key: string;
  type: MessageTypes;
  timestamp: number;
  seq?: number | null;
  ui_key?: string | null;
  samurai?: number | string; // Bot messages have samurai property
  extra?: {
    [key: string]: any;
    typing: 'auto' | 'custom';
    typing_delay: number;
  } | null;
  message?: string;
  title?: string;
  action?: string;
}
```

#### ConfigProperties

```typescript
interface ConfigProperties {
  // Firebase configuration
  firestore: {
    api_key: string;
    app_id: string;
    project_id: string;
  };
  firestoreSettings?: Record<string, unknown>;

  // Server configuration
  serverUrl: string | null;
  brandID: number;
  channelToken: string;
  welcomeUrl?: string | null;

  // Optional configuration
  landbotToken?: string | null; // Pre-existing Landbot token
  welcome?: Message[]; // Welcome messages
  storage_off?: boolean; // Disable local storage

  // Typing options
  typing_options?: {
    state: boolean;
    block_custom?: boolean;
    delay?: {
      is_constant: boolean;
      constant: number;
      average_human_reading_speed: number;
      max_delay: number;
    };
  };
}
```

## 🎨 Framework Examples

### React Typescript

```jsx
import { useEffect, useRef, useState } from 'react';
import { type ConfigProperties, Core, type Message, type Pipelines } from '@landbot/core';

function App() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const coreRef = useRef<Core | null>(null);

  useEffect(() => {
    let subscription: ReturnType<Pipelines['$readableSequence']['subscribe']>;

    // Fetch configuration from your chatbot share url
    fetch('https://landbot.pro/v3/H-3264766-KWEIO45R3L3OYPE1/index.json')
      .then((res) => res.json())
      .then((config: ConfigProperties) => {
        const core = new Core(config);
        coreRef.current = core;

        // Make sure you're subscribed to incoming messages before core initialization
        subscription = core.pipelines.$readableSequence.subscribe((message) => {
          setMessages((prev) => [...prev, message]);
        });

        return core.init();
      })
      .catch(console.error);

    return () => subscription?.unsubscribe();
  }, []);

  const handleSend = () => {
    if (coreRef.current && input.trim()) {
      coreRef.current.sendMessage({ type: 'text', message: input });
      setInput('');
    }
  };

  return (
    <div>
      <div className="messages">
        {messages.map((msg) => (
          <div key={msg.key} className="message">
            {msg.title ?? msg.message}
          </div>
        ))}
      </div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && handleSend()}
      />
      <button onClick={handleSend}>Send</button>
    </div>
  );
}
```