Coder Social home page Coder Social logo

clue / reactphp-ami Goto Github PK

View Code? Open in Web Editor NEW
75.0 10.0 37.0 148 KB

Streaming, event-driven access to the Asterisk Manager Interface (AMI), built on top of ReactPHP.

Home Page: https://clue.engineering/2019/introducing-reactphp-ami

License: MIT License

PHP 100.00%

reactphp-ami's Introduction

clue/reactphp-ami

CI status installs on Packagist

Streaming, event-driven access to the Asterisk Manager Interface (AMI), built on top of ReactPHP.

The Asterisk PBX is a popular open source telephony solution that offers a wide range of telephony features. The Asterisk Manager Interface (AMI) allows you to control and monitor the PBX. Among others, it can be used to originate a new call, execute Asterisk commands or monitor the status of subscribers, channels or queues.

  • Async execution of Actions - Send any number of actions (commands) to the Asterisk service in parallel and process their responses as soon as results come in. The Promise-based design provides a sane interface to working with out of order responses.
  • Event-driven core - Register your event handler callbacks to react to incoming events, such as an incoming call or a change in a subscriber state.
  • Lightweight, SOLID design - Provides a thin abstraction that is just good enough and does not get in your way. Future or custom actions and events require no changes to be supported.
  • Good test coverage - Comes with an automated tests suite and is regularly tested in the real world against current Asterisk versions and versions as old as Asterisk 1.8.

Table of contents

Support us

We invest a lot of time developing, maintaining and updating our awesome open-source projects. You can help us sustain this high-quality of our work by becoming a sponsor on GitHub. Sponsors get numerous benefits in return, see our sponsoring page for details.

Let's take these projects to the next level together! 🚀

Quickstart example

Once installed, you can use the following code to access your local Asterisk instance and issue some simple commands via AMI:

<?php

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

$factory = new Clue\React\Ami\Factory();

$factory->createClient('user:secret@localhost')->then(function (Clue\React\Ami\Client $client) {
    echo 'Client connected' . PHP_EOL;

    $sender = new Clue\React\Ami\ActionSender($client);
    $sender->listCommands()->then(function (Clue\React\Ami\Protocol\Response $response) {
        echo 'Available commands:' . PHP_EOL;
        var_dump($response);
    });
});

See also the examples.

Usage

Factory

The Factory is responsible for creating your Client instance.

$factory = new Clue\React\Ami\Factory();

This class takes an optional LoopInterface|null $loop parameter that can be used to pass the event loop instance to use for this object. You can use a null value here in order to use the default loop. This value SHOULD NOT be given unless you're sure you want to explicitly use a given event loop instance.

If you need custom connector settings (DNS resolution, TLS parameters, timeouts, proxy servers etc.), you can explicitly pass a custom instance of the ConnectorInterface:

$connector = new React\Socket\Connector(array(
    'dns' => '127.0.0.1',
    'tcp' => array(
        'bindto' => '192.168.10.1:0'
    ),
    'tls' => array(
        'verify_peer' => false,
        'verify_peer_name' => false
    )
));

$factory = new Clue\React\Ami\Factory(null, $connector);

createClient()

The createClient(string $url): PromiseInterface<Client,Exception> method can be used to create a new Client.

It helps with establishing a plain TCP/IP or secure TLS connection to the AMI and optionally issuing an initial login action.

$factory->createClient($url)->then(
    function (Clue\React\Ami\Client $client) {
        // client connected (and authenticated)
    },
    function (Exception $e) {
        // an error occurred while trying to connect or authorize client
    }
);

The method returns a Promise that will resolve with the Client instance on success or will reject with an Exception if the URL is invalid or the connection or authentication fails.

The $url parameter contains the host and optional port (which defaults to 5038 for plain TCP/IP connections) to connect to:

$factory->createClient('localhost:5038');

The above example does not pass any authentication details, so you may have to call ActionSender::login() after connecting or use the recommended shortcut to pass a username and secret for your AMI login details like this:

$factory->createClient('user:secret@localhost');

Note that both the username and password must be URL-encoded (percent-encoded) if they contain special characters:

$user = 'he:llo';
$pass = 'p@ss';

$promise = $factory->createClient(
    rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost'
);

The Factory defaults to establishing a plaintext TCP connection. If you want to create a secure TLS connection, you can use the tls scheme (which defaults to port 5039):

$factory->createClient('tls://user:secret@localhost:5039');

Client

The Client is responsible for exchanging messages with the Asterisk Manager Interface and keeps track of pending actions.

If you want to send outgoing actions, see below for the ActionSender class.

Besides defining a few methods, this interface also implements the EventEmitterInterface which allows you to react to certain events as documented below.

close()

The close(): void method can be used to force-close the AMI connection and reject all pending actions.

end()

The end(): void method can be used to soft-close the AMI connection once all pending actions are completed.

createAction()

The createAction(string $name, array $fields): Action method can be used to construct a custom AMI action.

This method is considered advanced usage and mostly used internally only. Creating Action objects, sending them via AMI and waiting for incoming Response objects is usually hidden behind the ActionSender interface.

If you happen to need a custom or otherwise unsupported action, you can also do so manually as follows. Consider filing a PR to add new actions to the ActionSender.

A unique value will be added to "ActionID" field automatically (needed to match the incoming responses).

$action = $client->createAction('Originate', array('Channel' => …));
$promise = $client->request($action);

request()

The request(Action $action): PromiseInterface<Response,Exception> method can be used to queue the given messages to be sent via AMI and wait for a Response object that matches the value of its "ActionID" field.

This method is considered advanced usage and mostly used internally only. Creating Action objects, sending them via AMI and waiting for incoming Response objects is usually hidden behind the ActionSender interface.

If you happen to need a custom or otherwise unsupported action, you can also do so manually as follows. Consider filing a PR to add new actions to the ActionSender.

$action = $client->createAction('Originate', array('Channel' => …));
$promise = $client->request($action);

event event

The event event (what a lovely name) will be emitted whenever AMI sends an event, such as a phone call that just started or ended and much more. The event receives a single Event argument describing the event instance.

$client->on('event', function (Clue\React\Ami\Protocol\Event $event) {
    // process an incoming AMI event (see below)
    var_dump($event->getName(), $event);
});

Event reporting can be turned on/off via AMI configuration and the events() action. The events() action can also be used to enable an "EventMask" to only report certain events as per the AMI documentation.

See also AMI Events documentation for more details about event types and their respective fields.

error event

The error event will be emitted once a fatal error occurs, such as when the client connection is lost or is invalid. The event receives a single Exception argument for the error instance.

$client->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

This event will only be triggered for fatal errors and will be followed by closing the client connection. It is not to be confused with "soft" errors caused by invalid commands.

close event

The close event will be emitted once the client connection closes (terminates).

$client->on('close', function () {
    echo 'Connection closed' . PHP_EOL;
});

See also the close() method.

ActionSender

The ActionSender wraps a given Client instance to provide a simple way to execute common actions. This class represents the main interface to execute actions and wait for the corresponding responses.

$sender = new Clue\React\Ami\ActionSender($client);

Actions

All public methods resemble their respective AMI actions.

$sender->login($name, $pass);
$sender->logoff();
$sender->ping();
$sender->command($command);
$sender->events($eventMask);

$sender->coreShowChannels();
$sender->sipPeers();
$sender->agents();

// many more…

Listing all available actions is out of scope here, please refer to the class outline.

Note that using the ActionSender is not strictly necessary, but is the recommended way to execute common actions.

If you happen to need a custom or otherwise unsupported action, you can also do so manually. See the advanced createAction() usage above for details. Consider filing a PR to add new actions to the ActionSender.

Promises

Sending actions is async (non-blocking), so you can actually send multiple action requests in parallel. The AMI will respond to each action with a Response object. The order is not guaranteed. Sending actions uses a Promise-based interface that makes it easy to react to when an action is completed (i.e. either successfully fulfilled or rejected with an error):

$sender->ping()->then(
    function (Clue\React\Ami\Protocol\Response $response) {
        // response received for ping action
    },
    function (Exception $e) {
        // an error occurred while executing the action
        
        if ($e instanceof Clue\React\Ami\Protocol\ErrorException) {
            // we received a valid error response (such as authorization error)
            $response = $e->getResponse();
        } else {
            // we did not receive a valid response (likely a transport issue)
        }
    }
});

All actions resolve with a Response object on success, some actions are documented to return the specialized Collection object to contain a list of entries.

Blocking

As stated above, this library provides you a powerful, async API by default.

If, however, you want to integrate this into your traditional, blocking environment, you should look into also using clue/reactphp-block.

The resulting blocking code could look something like this:

use Clue\React\Block;
use React\EventLoop\Loop;

function getSipPeers()
{
    $factory = new Clue\React\Ami\Factory();

    $target = 'name:password@localhost';
    $promise = $factory->createClient($target)->then(function (Clue\React\Ami\Client $client) {
        $sender = new Clue\React\Ami\ActionSender($client);
        $ret = $sender->sipPeers()->then(function (Clue\React\Ami\Collection $collection) {
            return $collection->getEntryEvents();
        });
        $client->end();
        return $ret;
    });

    return Block\await($promise, Loop::get(), 5.0);
}

Refer to clue/reactphp-block for more details.

Message

The Message is an abstract base class for the Response, Action and Event value objects. It provides a common interface for these three message types.

Each Message consists of any number of fields with each having a name and one or multiple values. Field names are matched case-insensitive. The interpretation of values is application-specific.

getFieldValue()

The getFieldValue(string $key): ?string method can be used to get the first value for the given field key.

If no value was found, null is returned.

getFieldValues()

The getFieldValues(string $key): string[] method can be used to get a list of all values for the given field key.

If no value was found, an empty array() is returned.

getFieldVariables()

The getFieldVariables(string $key): array method can be used to get a hashmap of all variable assignments in the given $key.

If no value was found, an empty array() is returned.

getFields()

The getFields(): array method can be used to get an array of all fields.

getActionId()

The getActionId(): string method can be used to get the unique action ID of this message.

This is a shortcut to get the value of the "ActionID" field.

Response

The Response value object represents the incoming response received from the AMI. It shares all properties of the Message parent class.

getCommandOutput()

The getCommandOutput(): ?string method can be used to get the resulting output of a "command" Action. This value is only available if this is actually a response to a "command" action, otherwise it defaults to null.

$sender->command('help')->then(function (Clue\React\Ami\Protocol\Response $response) {
    echo $response->getCommandOutput();
});

Collection

The Collection value object represents an incoming response received from the AMI for certain actions that return a list of entries. It shares all properties of the Response parent class.

You can access the Collection like a normal Response in order to access the leading Response for this collection or you can use the below methods to access the list entries and completion event.

Action: CoreShowChannels

Response: Success
EventList: start
Message: Channels will follow

Event: CoreShowChannel
Channel: SIP / 123
ChannelState: 6
ChannelStateDesc: Up
…

Event: CoreShowChannel
Channel: SIP / 456
ChannelState: 6
ChannelStateDesc: Up
…

Event: CoreShowChannel
Channel: SIP / 789
ChannelState: 6
ChannelStateDesc: Up
…

Event: CoreShowChannelsComplete
EventList: Complete
ListItems: 3

getEntryEvents()

The getEntryEvents(): Event[] method can be used to get the list of all intermediary Event objects where each entry represents a single entry in the collection.

foreach ($collection->getEntryEvents() as $entry) {
    assert($entry instanceof Clue\React\Ami\Protocol\Event);
    echo $entry->getFieldValue('Channel') . PHP_EOL;
}

getCompleteEvent()

The getCompleteEvent(): Event method can be used to get the trailing Event that completes this collection.

echo $collection->getCompleteEvent()->getFieldValue('ListItems') . PHP_EOL;

Action

The Action value object represents an outgoing action message to be sent to the AMI. It shares all properties of the Message parent class.

getMessageSerialized()

The getMessageSerialized(): string method can be used to get the serialized version of this outgoing action to send to Asterisk.

This method is considered advanced usage and mostly used internally only.

Event

The Event value object represents the incoming event received from the AMI. It shares all properties of the Message parent class.

getName()

The getName(): ?string method can be used to get the name of the event.

This is a shortcut to get the value of the "Event" field.

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version:

composer require clue/ami-react:^1.2

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+. It's highly recommended to use the latest supported PHP version for this project.

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

composer install

To run the test suite, go to the project root and run:

vendor/bin/phpunit

The test suite contains both unit tests and functional integration tests. The functional tests require access to a running Asterisk server instance and will be skipped by default. If you want to also run the functional tests, you need to supply your AMI login details in an environment variable like this:

LOGIN=username:password@localhost php vendor/bin/phpunit

License

This project is released under the permissive MIT license.

Did you know that I offer custom development services and issuing invoices for sponsorships of releases and for contributions? Contact me (@clue) for details.

reactphp-ami's People

Contributors

bonan avatar clue avatar glet avatar paulrotmann avatar simonfrings avatar yadaiio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactphp-ami's Issues

Library configuration for production deployment

Hi Christian,

I have been using this library on an application that i am making. I have stumbled on this library just a few weeks go and i must say that by far this is the most developer friendly library i have encountered among other asterisk php libraries. This is coming from a developer with less than 2months of experience working on Asterisk. All of the use cases that was required for my application was easily done. You made my work really easy. The most interesting part really for me is that there is just a minimal to zero configuration done on the Asterisk server. This might seem little but for every beginner out there it will be a huge relief since studying asterisk will take roughly a year in just to learn the basics of asterisk.

Now to my questions, is there a configuration to deploy this application? I am on the process of deploying my app into a production server and
i want to make sure that the event listeners should automatically register whenever the asterisk server restarts (if the connection was closed). Right now when the connection was closed, i have to manually
start the script for the event listeners to be registered.
I was thinking of using nohup to automate this process, but i want to hear your thoughts first for me to decide the optimal solution for deployment. Thanks!

How to show async response in web page? (blocking integration)

All reactphp videos make application by console. How do I integrate to my site? I want to create a table and show the connected peers for example. On the console I can see the peers but how do I get json for my html?

Please, I'm worn out of worry.

AMI connection persistence

Hi Christian,

I have setup an Asterisk Server with FreePbx and another server for the Asterisk Events Listeners.
So what i wanted to do is to be able to persist connections or be able to reconnect to the AMI after it was closed.

The way i am executing the event listeners is via an entrypoint through command line as a bash command Ex. ./doEventListeners or as a php script php doEventListeners.php

Everytime the AMI connection closes, i have to execute the similar script.
Is there a way i can efficiently do this? I'm open to suggestions.

Also, with the current setup (Server 1 with Asterisk and Server 2 for event listeners), i am not able to record call using MixMonitor action. However, when the Asterisk and event listeners are on a same server, MixMonitor just works fine. Is this the normal behavior?

Thanks

ari tcp connector object not resolving being vpn. manual dns necessary

Hi.
i have to connect to a vpn as asterisk 2.10 is behind it.
the promise to the connector does not resolve.

require '../vendor/autoload.php';
use React\Socket\ConnectionInterface;

$loop = React\EventLoop\Factory::create();
$connector = new React\Socket\Connector($loop);
//$dns = $dnsResolverFactory->createCached('10.10.30.21', $loop);

$connector
    ->connect('pbx-stg.**.com:5038')
    ->then(
        function (ConnectionInterface $conn)  {
            echo "Connection established\n";
        },
        function (Exception $exception) use ($loop){
            echo "Cannot connect to server: " . $exception->getMessage();
            $loop->stop();
        });

$loop->run();

there is a dns issue.
i have to hard code the dns.

$connector = new React\Socket\Connector($loop,array(
    'dns' => '10.10.30.21'
));

what am i doing wrong.
i am able to use the Ratchet Websocket library to make connection to ARI.
there the dns is automatically resolved.i read the source[https://github.com/ratchetphp/Pawl/blob/master/src/Connector.php] but was unable to understand how it is handled effectively.

any tips to resolve this would be wonderful

Thank You.

Catchable error on Client.php

Hi,

Not sure what happen, but couple days ago this was working perfect. Went to update today and I now get the following issue. Any ideas? How can I use composer to downgrade back to older version before any commits today?

PHP Catchable fatal error: Argument 1 passed to Clue\React\Ami\Client::__construct() must be an instance of React\Stream\StreamInterface, instance of React\Stream\Stream given, called in /var/www/projects/test/reactphp-asterisk-ami/src/Factory.php on line 43 and defined in /var/www/projects/test/reactphp-asterisk-ami/src/Client.php on line 23

running the example peers.php

Trying to set up the basic demo before using the library i get these error on peers.php.

php examples/peers.php umyser:[email protected]:5038

PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function Clue\React\Ami\Factory::__construct(), 0 passed in /var/www/html/react/examples/peers.php on line 5 and at least 1 expected in /var/www/html/react/vendor/clue/ami-react/src/Factory.php:42
Stack trace:
#0 /var/www/html/react/examples/peers.php(5): Clue\React\Ami\Factory->__construct()
#1 {main}
thrown in /var/www/html/react/vendor/clue/ami-react/src/Factory.php on line 42

Using version 1.1.0

[Question] Moving Stream from Client Constructor

Hey. Been using your library as reference. What would you say about moving the Stream $stream from the __construct() of Client. https://github.com/clue/php-ami-react/blob/master/src/Client.php#L25

Instead have a setStream method that can be called in here: https://github.com/clue/php-ami-react/blob/master/src/Factory.php#L41.

This opens up to making the Client instance before the Stream has connected. Then you can use ZeroMQ to send commands from a Web Interface for example as now you can reference something like

$zeromq->on('message', array($client, 'onMessage'));

Could there perhaps be something I'm missing?

Fix parsing empty command output

$api->command('!');
PHP Fatal error:  Uncaught exception 'UnexpectedValueException' with message 'Parse error, no colon in line "--END COMMAND--" found' in ~/workspace/reactphp-asterisk-ami/src/Protocol/Parser.php:51

Memory leak on client "events" callback?

I have a long lived script that listens to AMI events continuously. The script captures in its peak about 40 events per seconds so it leads to accumulate the memory and PHP do not free it, even if I add a timer loop for garbage collector method call gc_collect_cycles() with periodicity of 10 seconds.

The code is simple and it captures the event and publish it in redis, so the basic worker code is:

$client->on('event', function (Event $event) {
    $this->redisClient->publish("ami_event_{$event->getName()}", json_encode($event->getFields()));
    unset($event);
});

can I get some advice to avoid this problem? to bypass it I temporary added to restart the script daemon periodically, as it it runs out of memory and its about gigabytes)))

Client immediately disconnects (Asterisk 1.6.2.6)

Hello! I have no succes to use this library with my gsm gateway (Yeastar TG400) based on linux with asterisk 1.6.2.6.

Here is some details:
I copypasted this example to test.php:


include __DIR__."/vendor/autoload.php";
use Clue\React\Ami\Factory;
use Clue\React\Ami\Client;
use Clue\React\Ami\ActionSender;
use Clue\React\Ami\Protocol\Response;


$loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);
$target = isset($argv[1]) ? $argv[1] : '<MYUSER>:<MYPASS>@<MYHOST>;
$factory->createClient($target)->then(function (Client $client) use ($loop) {
    echo 'Client connected. Use STDIN to send CLI commands via asterisk AMI.' . PHP_EOL;
    $sender = new ActionSender($client);
    $sender->events(false);
    $sender->listCommands()->then(function (Response $response) {
        echo 'Commands: ' . implode(', ', array_keys($response->getFields())) . PHP_EOL;
    });
    $client->on('close', function() use ($loop) {
        echo 'Closed' . PHP_EOL;
        $loop->removeReadStream(STDIN);
    });
    $loop->addReadStream(STDIN, function () use ($sender) {
        $line = trim(fread(STDIN, 4096));
        echo '<' . $line . PHP_EOL;
        $sender->command($line)->then(
            function (Response $response) {
                echo $response->getCommandOutput() . PHP_EOL;
            },
            function (Exception $error) use ($line) {
                echo 'Error executing "' . $line . '": ' . $error->getMessage() . PHP_EOL;
            }
        );
    });
}, 'var_dump');
$loop->run();

On client:

Client connected. Use STDIN to send CLI commands via asterisk AMI.
Closed```


On server:
```Verbosity is at least 3
  == Manager '<CLIENTUSER>' logged on from <CLIENTHOST>
  == Manager '<CLIENTUSER>' logged off from <CLIENTHOST>
TG400*CLI> 

I've tryed to send command with $sender->("<command>")->... but have no succes too.

Can you help me to debug this?

Is php-ami-react ready for production?

Hi :)

I am just wondering how many concurrent calls have you tested with php-ami-react? is it ready for a production?

I would like to write a predictive dialler script and it will run 24/7 on the background. There will be about 200-300 concurrent calls and a lot of agents at the call center. Script should detect individual call event in real time.

A script would listen some events and do some actions with combination of mysql query (select / update).

Thanks!

createAction sends same ActionId with every call

createAction appends an ActionID to every call, but it is always returning the same value (2).
It does not seem to be possible to override it like this:
$client->createAction('Setvar',["Variable"=>"DEVICE_STATE(Custom:$device)","Value"=>$state,'ActionID' =>'102'])

Authentication fails if username/password contains characters that need to be URL encoded

Hello, when connecting to AMI using credentials that contain special characters like !@#$%^&*() authentication fails. When you encode the characters using urlencode, the URL is parsed properly by PHP, but this library does not decode the username and password before sending the login request over the AMI connection, so authentication fails.

https://github.com/clue/reactphp-ami/blob/master/src/Factory.php#L101

Solution:

Add urldecode to user and pass: https://github.com/clue/reactphp-ami/blob/master/src/Factory.php#L119

Expose command output via getter

The current way of handling command output is not particularly straightforward:

$api->command($command)->then(function (Response $response) {
    echo $response->getFieldValue('_') . PHP_EOL;
});

Support for Asterisk 14 and newer CLI commands

The output of commands has changed in Asterisk 14. The result of a CLI command is now shown in lines prefixed with "Output:".
This causes problems when using php-ami-react with newer versions of Asterisk. When a command is executed, the response is:

Error executing "DeviceStateList": Error "Command output follows"

which is because the actual Asterisk output looks like this:

Action: Command
Command: sip show user example

Response: Success
Message: Command output follows
Output: User example not found.
Output: 

The relevant Asterisk commit where this is described is asterisk/asterisk@2f418c052ec

Default to challenge response authentication (CRA)

Concept:

    public function loginChallenge($username, $secret, $events = null)
    {
        $events = $this->boolParam($events);
        $client = $this->client;
        return $this->challenge()->then(function (ActionResponse $response) use ($username, $secret, $client, $events) {
            $key = md5($response->getPart('Challenge') . $secret);
            return $client->request(new ActionRequest('Login', array('Username' => $username, 'Key' => $key, 'Events' => $events)));
        });
    }

Connection closed

$loop = \React\EventLoop\Factory::create();
$factory = new Factory($loop);
$factory->createClient($target)->then(
    function (Client $client) use ($loop) {
    echo 'Client connected ' . PHP_EOL;
    $sender = new ActionSender($client);
    $sender->events(true);
    $client->on('close', function() {
        echo 'Connection closed' . PHP_EOL;
    });
    $client->on('event', function (Event $event) {
        echo 'Event: ' . $event->getName() . ': ' . json_encode($event->getFields()) . PHP_EOL;
    });
    },
    function (\Exception $error) {
    echo 'Connection error: ' . $error;
    }
);
$loop->run();

Perform and immediately closes the connection:

Client connected
Connection closed

Error in generics

In client methods generics for PromiseInterface are wrong:

/**
 * @return \React\Promise\PromiseInterface<Response,\Exception>
 */

Response is not in the same namespace so it referers to an not existing class.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.