salesrender/plugin-core-chat

SalesRender plugin chat core

Installs: 224

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 2

Forks: 0

Open Issues: 0

pkg:composer/salesrender/plugin-core-chat

0.3.0 2024-01-05 14:09 UTC

This package is auto-updated.

Last update: 2026-02-13 21:01:20 UTC


README

Type-specific core framework for SalesRender CHAT plugins

Overview

salesrender/plugin-core-chat is a specialized core library that extends the base salesrender/plugin-core to build Chat-type plugins. Chat plugins handle sending and receiving messages to/from customers via various messaging platforms -- WhatsApp, Telegram, SMS, email, and other gateways.

This core provides:

  • A queue-based message sending architecture (ChatSendTask, ChatSendQueueCommand, ChatSendQueueHandleCommand)
  • A ChatSenderInterface that the developer must implement with actual gateway logic
  • An HTTP endpoint (POST /special/send) for receiving outgoing messages from SalesRender
  • A Chat::send() method for forwarding incoming messages back to SalesRender CRM
  • A MessageStatusSender for reporting delivery statuses (sent, delivered, read, error)
  • Automatic cron scheduling for processing the send queue

Installation

composer require salesrender/plugin-core-chat

Requirements

  • PHP >= 7.4
  • ext-json
  • salesrender/plugin-core ^0.4.1 (installed automatically)
  • salesrender/plugin-component-queue ^0.3.0 (installed automatically)
  • xakepehok/enum-helper ^0.1.1 (installed automatically)

Architecture

How This Core Extends plugin-core

plugin-core-chat overrides both factory classes from the base plugin-core:

WebAppFactory (extends \SalesRender\Plugin\Core\Factories\WebAppFactory):

  • Adds CORS support
  • Registers the SenderAction as a special request action, creating the route POST /special/send

ConsoleAppFactory (extends \SalesRender\Plugin\Core\Factories\ConsoleAppFactory):

  • Registers ChatSendQueueCommand (chatSendQueue:queue)
  • Registers ChatSendQueueHandleCommand (chatSendQueue:handle)
  • Adds a cron task that runs chatSendQueue:queue every minute

Message Flow

SalesRender CRM                          Chat Plugin                         External Gateway
      |                                       |                                      |
      |--- POST /special/send (outgoing) ---->|                                      |
      |                                       |--- ChatSendTask (queued) ----------->|
      |                                       |--- ChatSenderInterface::__invoke() -->|
      |                                       |                                      |
      |<-- MessageStatusSender::send() -------|<--- delivery status -----------------|
      |<-- Chat::send() (incoming) -----------|<--- incoming message ----------------|

Getting Started: Creating a Chat Plugin

Step 1: Project Setup

Create a new project and add the dependency:

mkdir my-chat-plugin && cd my-chat-plugin
composer init --name="myvendor/plugin-chat-mygateway" --type="project"
composer require salesrender/plugin-core-chat

Create the directory structure:

my-chat-plugin/
  bootstrap.php
  console.php
  composer.json
  example.env
  public/
    .htaccess
    index.php
    icon.png
  src/
    Sender/
      ChatSender.php
    Forms/
      SettingsForm.php
  db/
  runtime/

Step 2: Bootstrap Configuration

Create bootstrap.php in the project root. This file configures all plugin components:

<?php

use SalesRender\Plugin\Components\Db\Components\Connector;
use SalesRender\Plugin\Components\Form\Autocomplete\AutocompleteRegistry;
use SalesRender\Plugin\Components\Info\Developer;
use SalesRender\Plugin\Components\Info\Info;
use SalesRender\Plugin\Components\Info\PluginType;
use SalesRender\Plugin\Components\Settings\Settings;
use SalesRender\Plugin\Components\Translations\Translator;
use SalesRender\Plugin\Core\Actions\Upload\LocalUploadAction;
use SalesRender\Plugin\Core\Actions\Upload\UploadersContainer;
use SalesRender\Plugin\Core\Chat\SendMessageQueue\ChatSendQueueHandleCommand;
use SalesRender\Plugin\Instance\Chat\Forms\SettingsForm;
use SalesRender\Plugin\Instance\Chat\Sender\ChatSender;
use Medoo\Medoo;
use XAKEPEHOK\Path\Path;

# 0. Configure environment variable in .env file, that placed into root of app

# 1. Configure DB (for SQLite *.db file and parent directory should be writable)
Connector::config(new Medoo([
    'database_type' => 'sqlite',
    'database_file' => Path::root()->down('db/database.db')
]));

# 2. Set plugin default language
Translator::config('ru_RU');

# 3. Configure info about plugin
Info::config(
    new PluginType(PluginType::CHAT),
    fn() => Translator::get('info', 'Example chat plugin'),
    fn() => Translator::get('info', 'This plugin created only for demo purposes'),
    [
        "contactType" => $_ENV['LV_PLUGIN_CONTACT_TYPE'],
        "capabilities" => [
            "subject" => (bool) (int) $_ENV['LV_PLUGIN_CAPABILITY_SUBJECT'],
            "typing" => false,
            "messages" => [
                "formats" => empty($_ENV['LV_PLUGIN_MESSAGE_FORMATS'])
                    ? ['text']
                    : explode(' ', $_ENV['LV_PLUGIN_MESSAGE_FORMATS']),
                "incoming" => (bool) (int) $_ENV['LV_PLUGIN_MESSAGE_INCOMING'],
                "outgoing" => (bool) (int) $_ENV['LV_PLUGIN_MESSAGE_OUTGOING'],
                "writeFirst" => (bool) (int) $_ENV['LV_PLUGIN_MESSAGE_WRITE_FIRST'],
                "statuses" => [
                    "sent",
                    "delivered",
                    "read",
                    "error"
                ]
            ],
            "attachments" => empty($_ENV['LV_PLUGIN_ATTACHMENTS'])
                ? []
                : explode(' ', $_ENV['LV_PLUGIN_ATTACHMENTS']),
        ]
    ],
    new Developer(
        'Your Company',
        'support@example.com',
        'example.com',
    )
);

# Upload actions (for attachments support)
$uploader = new LocalUploadAction(['*' => 10 * 1024 * 1024]);
UploadersContainer::addDefaultUploader($uploader);
UploadersContainer::addCustomUploader('image', $uploader);
UploadersContainer::addCustomUploader('voice', $uploader);

# 4. Configure settings form
Settings::setForm(fn() => new SettingsForm());

# 5. Configure form autocompletes (or remove this block if not used)
AutocompleteRegistry::config(function (string $name) {
    return null;
});

# 6. Configure ChatSendQueueHandleCommand with your sender implementation
ChatSendQueueHandleCommand::config(new ChatSender());

Key chat-specific configuration points:

  • PluginType::CHAT -- identifies this plugin as a Chat type
  • contactType -- the type of contact identifier the gateway uses (e.g., "uri", "phone", "email")
  • capabilities -- declares what the messaging gateway supports: message formats (text, markdown, html), incoming/outgoing messages, write-first capability, attachment types (file, image, voice), and message statuses
  • ChatSendQueueHandleCommand::config() -- registers your ChatSenderInterface implementation

Step 3: Implement ChatSenderInterface

This is the core of your chat plugin. Create a class that implements ChatSenderInterface:

<?php

namespace SalesRender\Plugin\Instance\Chat\Sender;

use SalesRender\Plugin\Components\Settings\Settings;
use SalesRender\Plugin\Core\Chat\Components\Chat\Chat;
use SalesRender\Plugin\Core\Chat\Components\Chat\Message\Message;
use SalesRender\Plugin\Core\Chat\Components\Chat\Message\MessageContent;
use SalesRender\Plugin\Core\Chat\Components\MessageStatusSender\MessageStatusSender;
use SalesRender\Plugin\Core\Chat\SendMessageQueue\ChatSenderInterface;
use SalesRender\Plugin\Core\Chat\SendMessageQueue\ChatSendTask;

class ChatSender implements ChatSenderInterface
{

    public function __invoke(ChatSendTask $task)
    {
        $chat = $task->getChat();
        $message = $chat->getMessage();

        $setting = Settings::find()->getData();

        // 1. Extract message content and attachments
        $content = $message->getContent();   // MessageContent|null
        $attachments = $message->getAttachments(); // MessageAttachment[]

        // 2. Send the message via your gateway API
        //    Use $chat->getContact() for the recipient identifier
        //    Use $chat->getSubject() for conversation subject (if supported)
        //    Use $content->getText() and $content->getFormat() for message text
        //    Use $attachments for files/images/voice
        $gatewayMessageId = $this->sendViaGateway($chat);

        // 3. Report message status back to SalesRender
        MessageStatusSender::send($message->getId(), MessageStatusSender::SENT);

        // Later, when you receive delivery confirmation from the gateway:
        // MessageStatusSender::send($message->getId(), MessageStatusSender::DELIVERED);
        // MessageStatusSender::send($message->getId(), MessageStatusSender::READ);

        // On error:
        // MessageStatusSender::send($message->getId(), MessageStatusSender::ERROR);
    }

    private function sendViaGateway(Chat $chat): string
    {
        // Your gateway-specific sending logic here
        return 'gateway-message-id';
    }
}

The __invoke() method receives a ChatSendTask that wraps a Chat object. If the method throws an exception, the task will be retried (up to 100 attempts with a 10-second interval). After all attempts are exhausted, the task is deleted.

Step 4: Create Web Entry Point

Create public/index.php:

<?php
use SalesRender\Plugin\Core\Chat\Factories\WebAppFactory;

require_once __DIR__ . '/../vendor/autoload.php';

$factory = new WebAppFactory();
$application = $factory->build();
$application->run();

Create public/.htaccess:

RewriteEngine On
RewriteRule ^output - [L]
RewriteRule ^uploaded - [L]
RewriteCond %{REQUEST_FILENAME}  -f [OR]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L,QSA]

Step 5: Create Console Entry Point

Create console.php:

#!/usr/bin/env php
<?php

use SalesRender\Plugin\Core\Chat\Factories\ConsoleAppFactory;

require __DIR__ . '/vendor/autoload.php';

$factory = new ConsoleAppFactory();
$application = $factory->build();
$application->run();

Step 6: Create Settings Form

Create src/Forms/SettingsForm.php:

<?php

namespace SalesRender\Plugin\Instance\Chat\Forms;

use SalesRender\Plugin\Components\Form\FieldDefinitions\FieldDefinition;
use SalesRender\Plugin\Components\Form\FieldDefinitions\StringDefinition;
use SalesRender\Plugin\Components\Form\FieldGroup;
use SalesRender\Plugin\Components\Form\Form;
use SalesRender\Plugin\Components\Form\FormData;
use SalesRender\Plugin\Components\Translations\Translator;

class SettingsForm extends Form
{

    public function __construct()
    {
        $nonNull = function ($value, FieldDefinition $definition, FormData $data) {
            $errors = [];
            if (is_null($value)) {
                $errors[] = Translator::get('settings', 'Field can not be empty');
            }
            return $errors;
        };
        parent::__construct(
            Translator::get('settings', 'Settings'),
            Translator::get('settings', 'My Chat Plugin'),
            [
                'main' => new FieldGroup(
                    Translator::get('settings', 'Main'),
                    null,
                    [
                        'api_key' => new StringDefinition(
                            Translator::get('settings', 'API Key'),
                            Translator::get('settings', 'Your gateway API key'),
                            $nonNull,
                        ),
                    ]
                ),
            ],
            Translator::get('settings', 'Save'),
        );
    }
}

Step 7: Create .env

Create example.env (copy to .env for local development):

LV_PLUGIN_DEBUG=1
LV_PLUGIN_PHP_BINARY=php
LV_PLUGIN_SELF_URI=http://plugin-example/
LV_PLUGIN_COMPONENT_HANDSHAKE_SCHEME=http
LV_PLUGIN_COMPONENT_HANDSHAKE_HOSTNAME=lv-app

LV_PLUGIN_CONTACT_TYPE=uri
LV_PLUGIN_CAPABILITY_SUBJECT=1

LV_PLUGIN_MESSAGE_FORMATS="html text markdown"

LV_PLUGIN_MESSAGE_INCOMING=1
LV_PLUGIN_MESSAGE_OUTGOING=1
LV_PLUGIN_MESSAGE_WRITE_FIRST=1

LV_PLUGIN_ATTACHMENTS="file image voice"

Environment variables explained:

Variable Description
LV_PLUGIN_CONTACT_TYPE Type of contact identifier: uri, phone, email, etc.
LV_PLUGIN_CAPABILITY_SUBJECT 1 if conversation subjects are supported
LV_PLUGIN_MESSAGE_FORMATS Space-separated list: text, markdown, html
LV_PLUGIN_MESSAGE_INCOMING 1 if the plugin can receive incoming messages
LV_PLUGIN_MESSAGE_OUTGOING 1 if the plugin can send outgoing messages
LV_PLUGIN_MESSAGE_WRITE_FIRST 1 if the plugin can initiate conversations
LV_PLUGIN_ATTACHMENTS Space-separated list: file, image, voice
LV_PLUGIN_CHAT_SEND_QUEUE_LIMIT Max tasks per queue cycle (default: 100)

Step 8: Initialize & Deploy

# Install dependencies
composer install

# Create database tables
php console.php db:create

# Start cron (handles message sending queue)
php console.php cron

HTTP Routes

Routes added by \SalesRender\Plugin\Core\Chat\Factories\WebAppFactory:

Method Path Description Source
POST /special/send Receives outgoing messages from SalesRender CRM. Parses the message into a Chat object, creates a ChatSendTask, and queues it for sending. Returns 202 Accepted. SenderAction

Additionally, all base plugin-core routes are inherited:

Method Path Description
GET /info Plugin information
PUT /registration Plugin registration
GET /protected/forms/settings Settings form definition
PUT /protected/data/settings Save settings
GET /protected/data/settings Get settings data
GET /protected/autocomplete/{name} Autocomplete handler
POST /protected/upload File upload
GET /robots.txt Robots.txt

CLI Commands

Commands added by \SalesRender\Plugin\Core\Chat\Factories\ConsoleAppFactory:

Command Description
chatSendQueue:queue Finds pending ChatSendTask models and spawns child processes to handle them. Automatically scheduled via cron (every minute).
chatSendQueue:handle {id} Handles a single ChatSendTask by invoking the configured ChatSenderInterface. Called by the queue command.

Inherited base commands:

Command Description
db:create Create database tables
db:clean Clean database tables
specialRequest:queue Process special request queue
specialRequest:handle Handle a special request
cron Run all scheduled cron tasks
lang:add Add a translation language
lang:update Update translations
directory:clean Clean temporary directories

Key Classes & Interfaces

ChatSenderInterface

Namespace: SalesRender\Plugin\Core\Chat\SendMessageQueue\ChatSenderInterface

The primary interface that every chat plugin must implement. It defines a single invokable method:

interface ChatSenderInterface
{
    /**
     * Handle sending a message via the gateway and manage message status tracking.
     * @param ChatSendTask $task
     * @return mixed
     */
    public function __invoke(ChatSendTask $task);
}

Chat

Namespace: SalesRender\Plugin\Core\Chat\Components\Chat\Chat

Represents a chat conversation with a message. Implements JsonSerializable.

Method Return Type Description
__construct(?string $id, string $contact, ?string $subject, Message $message) Create a new Chat instance
getId() ?string Chat identifier (null for new chats)
getContact() string Contact identifier (phone, email, URI, etc.)
getSubject() ?string Conversation subject
getMessage() Message The message object
send() void Send this chat as an incoming message to SalesRender CRM
parseFromArray(array $chat) static Static factory: parse a Chat from an array

Chat::send() sends incoming messages (from the external gateway) to SalesRender CRM via a SpecialRequest to the path CRM/plugin/chat/incoming.

Message

Namespace: SalesRender\Plugin\Core\Chat\Components\Chat\Message\Message

Represents a single message. A message must have either content or at least one attachment.

Method Return Type Description
__construct(?string $id, ?MessageContent $content, array $attachments = []) Create a new Message. Throws EmptyMessageException if both content and attachments are empty.
getId() ?string Message identifier
getContent() ?MessageContent Text content of the message
getAttachments() MessageAttachment[] Array of attachments

MessageContent

Namespace: SalesRender\Plugin\Core\Chat\Components\Chat\Message\MessageContent

Holds the text body of a message with its format.

Method Return Type Description
__construct(string $text, string $format) Create content. Throws EmptyMessageContentException if text is empty.
getText() string The text body
getFormat() string Format: MessageContent::PLAIN_TEXT, MessageContent::MARKDOWN, or MessageContent::HTML

Format constants:

Constant Value
MessageContent::PLAIN_TEXT "text"
MessageContent::MARKDOWN "markdown"
MessageContent::HTML "html"

MessageAttachment

Namespace: SalesRender\Plugin\Core\Chat\Components\Chat\Message\MessageAttachment

Represents a file attachment. Extends EnumHelper for type validation.

Method Return Type Description
__construct(string $name, string $uri, string $type) Create an attachment. Type must be one of the valid values.
getName() string File name
getUri() string File URI
getType() string Attachment type

Type constants:

Constant Value
MessageAttachment::FILE "file"
MessageAttachment::IMAGE "image"
MessageAttachment::VOICE "voice"

ChatSendTask

Namespace: SalesRender\Plugin\Core\Chat\SendMessageQueue\ChatSendTask

A queue task that wraps a Chat object for deferred sending. Extends Task from plugin-component-queue.

Method Return Type Description
__construct(Chat $chat) Create a task with 100 max attempts and 10-second retry interval
getChat() Chat Get the wrapped Chat object
getAttempt() TaskAttempt Get the attempt tracker

MessageStatusSender

Namespace: SalesRender\Plugin\Core\Chat\Components\MessageStatusSender\MessageStatusSender

Static utility for reporting message delivery statuses back to SalesRender CRM. Sends a PATCH request to CRM/plugin/chat/status.

MessageStatusSender::send(string $messageId, string $status): void

Status constants:

Constant Value Description
MessageStatusSender::SENT "sent" Message was sent to the gateway
MessageStatusSender::DELIVERED "delivered" Message was delivered to the recipient
MessageStatusSender::READ "read" Message was read by the recipient
MessageStatusSender::ERROR "error" Message sending failed

SenderAction

Namespace: SalesRender\Plugin\Core\Chat\Actions\SenderAction

HTTP action that handles POST /special/send. Extends SpecialRequestAction. Parses the incoming request body into a Chat object and creates a ChatSendTask in the queue. Returns:

  • 202 Accepted -- message queued successfully
  • 405 -- invalid or empty message data
  • 500 -- error saving the queue task

Message Queue

The chat core uses a queue-based architecture for reliable message delivery:

ChatSendQueueCommand

Runs every minute via cron. Fetches pending ChatSendTask records from the database (respecting LV_PLUGIN_CHAT_SEND_QUEUE_LIMIT, default 100) and spawns up to 25 parallel child processes.

ChatSendQueueHandleCommand

Handles a single task by ID. Loads the ChatSendTask, sets the plugin reference context, and invokes the configured ChatSenderInterface. On success, the task is deleted. On failure, the attempt counter is incremented and the task is retried. After all attempts are exhausted (100 attempts), the task is deleted.

Configuration

Register your sender in bootstrap.php:

ChatSendQueueHandleCommand::config(new ChatSender());

Receiving Incoming Messages

If your plugin also receives messages from the external gateway (e.g., via webhook or API polling), you need to construct a Chat object and call Chat::send():

Via webhook -- create a custom action implementing ActionInterface and register it in the WebAppFactory:

// In your incoming message webhook handler:
$chat = new Chat(
    null,                    // id (null for new incoming messages)
    $contactIdentifier,      // the sender's contact identifier
    $subject,                // conversation subject or null
    new Message(
        null,                // message id (null for new messages)
        new MessageContent($text, MessageContent::PLAIN_TEXT),
        []                   // attachments
    )
);
$chat->send();

Via API polling -- create a custom console command and add it to the cron schedule.

Exceptions

Exception Description
EmptyMessageException Thrown when creating a Message with neither content nor attachments
EmptyMessageContentException Thrown when creating MessageContent with empty text

Example Plugin

See the reference implementation: plugin-chat-example

plugin-chat-example/
  bootstrap.php           -- Plugin configuration & ChatSender registration
  console.php             -- Console entry point (ConsoleAppFactory)
  public/
    index.php             -- Web entry point (WebAppFactory)
    .htaccess             -- Apache rewrite rules
    icon.png              -- Plugin icon
  src/
    Sender/
      ChatSender.php      -- ChatSenderInterface implementation
    Forms/
      SettingsForm.php     -- Settings form definition
  db/                     -- SQLite database directory
  runtime/                -- Runtime files directory
  example.env             -- Environment variables template

Dependencies

Package Version Purpose
salesrender/plugin-core ^0.4.1 Base plugin framework
salesrender/plugin-component-queue ^0.3.0 Queue system for task processing
xakepehok/enum-helper ^0.1.1 Enum validation for attachment types and statuses

See Also