npm package: @psnext/block-sdk
Description: Block SDK for CoreAI blocks
Author: CoreAI
Latest version: 0.0.32

# Block SDK

**Block SDK** is a utility designed to simplify Block development on the CoreAI platform. With Block SDK, developers can securely register blocks with the CoreAI platform and facilitate two-way communication between other blocks and front-end/back-end (FE/BE) components within the ecosystem. Since blocks are rendered as iframes within the CoreAI canvas, managing communication with other components often leads to duplicate code. Block SDK abstracts this complexity, providing reusable utilities and streamlining block development.

Once registered with Block SDK, developers no longer need to worry about the nuances of communication across the CoreAI platform. In addition to communication utilities, Block SDK offers features such as Event Manager, Fetcher, State Manager, Assistant Agent, ResizeObserver, and more, simplifying development efforts.

> **Note**: This SDK is a client-side SDK intended exclusively for use with the CoreAI platform.

> **Migration Note**: If you are migrating from a previous version (0.0.10 or earlier) of the Block SDK to the latest version, please be aware of the following changes:
> - The `register` method has been updated to return a Promise that resolves to an instance of the Block SDK.
> - The `register` method may now throw errors if block authentication fails.
> - Ensure you update your code to handle the asynchronous nature of the new `register` method and implement proper error handling.

## Table of Contents

- [Installation](#installation)
- [Features](#features)
- [Usage](#usage)
  - [Register the Block SDK](#register-the-block-sdk)
  - [Send and Receive Data Between Blocks](#send-and-receive-data-between-blocks)
  - [Send and Receive Data with ALX Chat](#send-and-receive-data-with-alx-chat)
  - [Receive App Data from Host App](#receive-app-data-from-host-app)
  - [Resize Observer](#resize-observer)
  - [Fetcher](#fetcher)
  - [State Manager](#state-manager)
- [Host Feature](#host-feature)
- [Contributing](#contributing)

## Installation

Block SDK is integrated into blocks via a CDN URL. Below is an example implementation using Next.js:

```typescript
import Script from 'next/script';
import Block from "./components/Block";

export default function Home() {
  return (
    <main>
      <Script
        src="https://unpkg.com/@psnext/block-sdk@0.0.25/dist/block-sdk.js"
        strategy="beforeInteractive"
      />
      <Block />
    </main>
  );
}
```

## Features

1. **Two-way Communication**:
   Blocks can send and receive data from other blocks and FE/BE components (e.g., ALX, Host) on the CoreAI platform.
2. **Fetcher Utility**:
   A wrapper around the browser fetch API, with integrated CoreAI authentication, token validation, and auto-refresh functionality.
3. **ResizeObserver Utility**:
   A wrapper for the browser's `ResizeObserver` API to track block dimension changes and communicate new sizes to the CoreAI platform, ensuring dynamic resizing of block containers.
4. **Logger Utility**:
   A logging utility that prefixes messages with CoreAI-specific details, offering multiple log levels and runtime configuration to control log output.
5. **State Manager**:
   A utility for managing and persisting block state across sessions in build and play mode.
6. **Alx Chat Assistant Agent**:
   BlockSDK provides seamless integration with Enterprise Assistant Agent, enabling AI-powered interactions within blocks. When a block has an `assistantId` configured in its `blocks.json`, the SDK automatically handles bidirectional communication between ALX chat and the Assistant Agent. Messages from ALX chat are processed and routed to the appropriate Assistant Agent, and responses are displayed back in the ALX chat window. 
   
   Key features:
   - Status tracking via `onAssistantUpdate` callback
   - Configurable origin server via `AIAssistant.setOrigin()`
   - Automatic status updates during message processing (in_progress, completed, error)
   
   To enable this integration, obtain an `assistantId` from the Enterprise Support team and add it to your block's `blocks.json` configuration.
7. **Additional Utilities**:
   - `getAppHostname`: Retrieves the app's hostname, returning the parent hostname if running inside an iframe.
   - `getHostFromUrl`: Extracts the hostname from a given URL.
   - `getAppUrl`: Retrieves the app's URL.
   - `getParentUrl`: Retrieves the parent app's URL.
8. **Future Features**:
   The SDK is designed to expand, with planned utilities like NotificationManager, Analytics, and Block Runner.

## Usage

### Register the Block SDK

Block SDK is available through the `window` object. To register a block with Block SDK, use the `register` method and provide a callback. The method returns an instance with all necessary tools for communication with other blocks and CoreAI components.

**Note:** During block registration, `BlockSdk` verifies user authentication. If the user is not authenticated, they will be automatically redirected to the login screen.

Example:

```typescript
useEffect(() => {
    if (window.BlockSdk) {
      (async () => {
        const instance = await window.BlockSdk.register({
          onIncomingData: onIncomingData,
          onHostDataUpdate: onHostDataUpdate,
          onAlxData: onAlxData,
          onAssistantUpdate: (status, ...args) => {
            // Handle assistant status updates
            // status can be: 'in_progress', 'completed', 'error'
            console.log('Assistant status:', status);
          }
        });
        if (instance) {
          instance.resizeObserver(parentRef.current);
          blockSdkInstance.current = instance;
          
          // Optionally configure assistant origin
          window.BlockSdk.AIAssistant.setOrigin('https://your-custom-origin');
        }
      })();
    }
  }, []);
```

### Send and Receive Data Between Blocks

Blocks can send and receive data to/from other blocks. Use the `sendOutput` method to send data:

@outputHandleId: string, // The handle id of the output to send. This is defined in the blocks.json.
@outputData: any // The data to send to the output

```typescript
function sendOutput(outputHandleId: string) {
  blockSdkInstance.current?.sendOutput(outputHandleId, outputData[outputHandleId]);
}
```

Handle incoming data using the `onIncomingData` callback:

```typescript
function onIncomingData(event: any, data: any) {
  console.log('On Incoming Data', data);
}
```

JSON structure of incoming data:

```json
{
  "receivingBlockId": "0e86c994-d91f-4fe2-b8c7-7122d94c97af",
  "type": "input-event",
  "eventData": {
    "version": 2,
    "eventId": "625b593b-b871-4700-8bc0-81f915f23625",
    "timestamp": 1727734592244,
    "playId": "",
    "source": {
      "blockId": "7444e179-4171-4d0b-a7b5-abe247d6cb21",
      "blockName": "X1 Local",
      "handleId": "x1-output-1"
    },
    "payload": {
      "data": "111",
      "lastUpdatedTimestamp": 1727734592244,
      "type": "string"
    },
    "targetHandleId": "x1-input-1"
  }
}
```

### Send and Receive Data with ALX Chat

Use `sendAlxMessage` to send messages to ALX Chat:

@alxMessage: any, // A message to send to ALX Chat. It could be any data type.
@alxEventType: string, // Optional. The type of event to send to ALX Chat.

```typescript
function sendDataToAlx() {
  blockSdkInstance.current?.sendAlxMessage(alxMessage);
}
```

To update an existing message:

```typescript
export const ALX_EVENT_TYPE = {
  UPDATE_MESSAGE: 'UPDATE_MESSAGE',
  DISPLAY_MESSAGE: 'DISPLAY_MESSAGE',
};
function sendDataToAlx() {
  blockSdkInstance.current?.sendAlxMessage(alxMessage, ALX_EVENT_TYPE.UPDATE_MESSAGE);
}
```

Handle incoming ALX Chat data with `onAlxData`:

```typescript
function onAlxData(event: any, eventData: any) {
  console.log('ALX Chat data', eventData);
}
```

JSON structure of ALX Chat data:

```json
{
  "receivingBlockId": "0e86c994-d91f-4fe2-b8c7-7122d94c97af",
  "type": "alx-event",
  "eventData": {
    "version": 2,
    "eventId": "826d5b1f-e28b-4121-8be0-2664df2cc712",
    "timestamp": 1727734713896,
    "playId": "",
    "payload": { "prompt": "hello" }
  }
}
```

### Receive App Data from Host App

The `onHostDataUpdate` callback is triggered when the block is loaded or when connecting edges are updated:

```typescript
function onHostDataUpdate(event: any, eventData: any) {
  console.log('Host data updated', eventData);
}
```

**Note: The `onHostDataUpdate` callback is triggered multiple times when the block is loaded. The first time it is triggered with the initial host data and then each time the edges are updated also it is called when the block is switched to different mode and version updates.**

JSON structure of host data:

```json
{
  "receivingBlockId": "7444e179-4171-4d0b-a7b5-abe247d6cb21",
  "type": "host-data-event",
  "eventData": {
    "version": 2,
    "eventId": "be3680a6-71c0-4acc-9c05-5a6544d483ce",
    "timestamp": 1727734340261,
    "playId": "",
    "payload": {
      "incomingEdges": [
        {
          "edgeId": "reactflow__edge-7444e179-4171-4d0b-a7b5-abe247d6cb21x1-output-1-0e86c994-d91f-4fe2-b8c7-7122d94c97afx1-input-1",
          "sourceHandleId": "x1-output-1",
          "targetHandleId": "x1-input-1",
          "sourceBlockId": "7444e179-4171-4d0b-a7b5-abe247d6cb21",
          "targetBlockId": "0e86c994-d91f-4fe2-b8c7-7122d94c97af"
        }
      ],
      "workspace": {
        "id": "05ff6020-3412-4827-a079-77a50824765d",
        "state": {
            "publishedVersionId": "6a5e3c68-3750-468f-8b6b-0d741e6a6169",
            "runId": "b6020ad4-ef3c-45d6-ba94-1a6b9e4eef7d",
            "createBuildState": false,
            "fetchBuildState": false,
            "createPlayState": false,
            "fetchPlayState": true
        },
        "mode": "play"
      },
      "project": {
        "id": "fe5c5203-97d2-43e8-a656-5ac7103ef13a"
      },
      "client": {
        "id": "4a18468e-f01b-4729-9a5b-4fb707755b41"
      },
      "appConfig": {
        "id": "7444e179-4171-4d0b-a7b5-abe247d6cb21",
        "agentUrl": "",
        "name": "X1 Local",
        "description": "X1 App",
        "appSlug": "x11",
        "width": 500,
        "height": 400,
        "defaultWidth": 1000,
        "defaultHeight": 900,
        "display_name": "X1",
        "preview_url": "/icons/Audience Builder.svg",
        "version": 2,
        "widget_url": "http://localhost:3003/web/x1",
        "miniapp_url": "http://localhost:3003/web/x1",
        "inputHandlers": [
          {
            "id": "x1-input-1",
            "name": "X1 Input 1",
            "description": "X1 Input 1",
            "type": "string"
          },
          {
            "id": "x1-input-2",
            "name": "X1 Input 2",
            "description": "X1 Input 2",
            "type": "string"
          }
        ],
        "outputHandlers": [
          {
            "id": "x1-output-1",
            "name": "X1 Output 1",
            "description": "X1 Output 1",
            "type": "string"
          },
          {
            "id": "x1-output-2",
            "name": "X1 Output 2",
            "description": "X1 Output 2",
            "type": "string"
          }
        ]
      }
    }
  }
}
```

### Resize Observer

Use Block SDK's ResizeObserver utility to monitor block dimensions:

@blockContainer: HTMLElement, // The block container element

```typescript
blockSdkInstance.current.resizeObserver(document.getElementById('block-container'));
```

### Fetcher

Block SDK's Fetcher utility handles network requests with authentication and token refresh. Its signature matches the browser fetch API:

@url: string, // The URL to fetch
@options: RequestInit, // Optional. The request options

```typescript
BlockSdk.Utils.fetch(url, options);
```

@requestCompleteResponse: Optional parameter: if true, the response is returned as is, else the custom error will be thrown

```typescript
BlockSdk.Utils.fetch(url, options, true);
```

### State Manager

The State Manager utility in BlockSDK allows blocks to persist and retrieve state across sessions. This ensures continuity of data between build and run modes, facilitating smoother experiences for users.

##### Usage

To integrate the State Manager, pass callback handlers during the `BlockSdk.register` call:

```typescript
useEffect(() => {
  if (window.BlockSdk) {
    (async () => {
      const instance = await window.BlockSdk.register({
        onBuildVersionUpdate: handleBuildVersionUpdate,
        onPlayInstanceUpdate: handlePlayInstanceUpdate,
      });
    })();
  }
}, []);
```

These callbacks are invoked whenever a new version is published in either play or build mode. Each callback is provided with a configuration object containing the following properties:

```typescript
interface IStateConfig {
  publishedVersionId: string
  runId: string
  createBuildState: boolean
  fetchBuildState: boolean
  createPlayState: boolean
  fetchPlayState: boolean
}
```

Each flag indicates whether the corresponding action (create or fetch state) should be performed. If set to `true`, the relevant state method is triggered.

##### Handling Build Version Updates

```typescript
const handleBuildVersionUpdate = async (config: IStateConfig) => {
  if (config.fetchBuildState) {
    const buildState = await blockSdkInstance.current.stateManager.fetchBuildState();
    if (buildState) {
      // Restore the UI with the fetched build state
    }
  }

  if (config.createBuildState) {
    await blockSdkInstance.current.stateManager.createBuildState({
      state: localBlockState,
    });
  }
};
```

##### Handling Play Instance Updates

```typescript
const handlePlayInstanceUpdate = async (config: IStateConfig) => {
  window.BlockSdk.Utils.logger.log("Play Instance Updated", config);
  let buildState;
  if(config.fetchBuildState) {
    buildState = await blockSdkInstance.current.stateManager.fetchBuildState();
    if(buildState) {
      // Restore the UI with the fetched build state
    }
  }
  if(config.createPlayState && buildState) {
    await blockSdkInstance.current.stateManager.createPlayState({...buildState});
  }
  if(config.fetchPlayState) {
    const playState = await blockSdkInstance.current.stateManager.fetchPlayState();
    if(playState) {
      // Restore the UI with the fetched play state
    }
  }
};
```

##### Available Methods

The State Manager provides the following methods:

* **fetchBuildState** : Retrieves the build state.
* **createBuildState** : Create the build state.
* **fetchPlayState** : Retrieves the play state.
* **createPlayState** : Create the play state.
* **updatePlayState** : Update the play state.

##### Local Development

For local development, you can use the local proxy server to proxy API requests to the CoreAI dev server. For example, if your local block runs on `http://localhost:3003/web/x1`, set `apiBase` to `http://localhost:3003/web/x1/dev-proxy-api`.

```typescript
  await blockSdkInstance.current.stateManager.createPlayState({
    state: buildState,
    apiBase: 'http://localhost:3003/web/x1/dev-proxy-api',
  });
```

Here's an example of configuring Next.js to proxy API requests to the CoreAI dev server:

```typescript
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/dev-proxy-api/:path*',
        destination: 'https://dev.lionis.ai/:path*',
      },
    ];
  },
}
```

## Host Feature

The Block SDK includes a host feature that allows you to run a local development environment for testing blocks. This feature downloads and sets up a host application that can render and manage blocks built with the Block SDK.

### Installation

The host feature is available through the CLI command:

```bash
npx @psnext/block-sdk run host [options]
```

### Options

- `-v<number>`: Specify the version to download (e.g., `-v0` for the latest version, `-v1` for the second latest, etc.)
- `-reset`: Reset the specified version by deleting existing files and downloading again
- `-help`: Display help information

### Features

1. **Automatic Version Management**:
   - Downloads the latest version of the host application
   - Maintains multiple versions locally
   - Allows switching between different versions

2. **Environment Setup**:
   - Automatically configures the host environment
   - Sets up necessary environment variables
   - Creates required directories and files

3. **Block Integration**:
   - Creates a `.coreai` directory in your project root
   - Copies block configurations to the host's public directory
   - Enables seamless integration between blocks and the host

4. **Progress Tracking**:
   - Shows download progress
   - Displays file copying progress
   - Provides clear status messages

5. **Cleanup and Maintenance**:
   - Automatically cleans up temporary files
   - Handles version-specific directories
   - Manages server process termination

### Example Usage

1. Start the host with the latest version:
```bash
npx @psnext/block-sdk run host
```

2. Use a specific version:
```bash
npx @psnext/block-sdk run host -v1
```

3. Reset and download a specific version:
```bash
npx @psnext/block-sdk run host -v2 -reset
```

### Directory Structure

When you run the host, it creates the following structure:

```
your-project/
├── .coreai/              # Block configurations
│   └── blocks.json        # Block definitions
└── temp/                # Temporary files
    └── versions/        # Downloaded host versions
        └── v<timestamp>/ # Version-specific files
```

### Notes

- The host application runs on `http://localhost:3001` by default
- Each version is identified by its timestamp
- The host automatically handles block communication and state management
- Use Ctrl+C to gracefully stop the host server

## Troubleshotting

**Property 'BlockSdk' does not exist on type 'Window & typeof globalThis'.ts(2339)**

To fix this add BlockSdk in global

```typescript
declare global {
  interface Window {
    BlockSdk: any;
  }
}
```

## Contributing

To contribute and run the package locally, follow these steps:

1. Clone the repository.
2. Run `yarn install`.
3. Run `yarn dev`
4. Load SDK as module http://localhost:3008/src/index.ts

### Available Scripts:

- `dev`: Build for production & start the development server.
- `build`: Build for production.
- `release`: Generate changelog and publish to npm.
- `lint`: Check for linting errors.
- `test`: Run tests.
- `test:watch`: Run tests in watch mode.
- `test:coverage`: Run tests and generate a coverage report.
- `prepare`: Set up Husky hooks.