feat: 新增WebSocket 服务
This commit is contained in:
972
src/addon/aikefu/api/controller/WebSocket.php
Normal file
972
src/addon/aikefu/api/controller/WebSocket.php
Normal file
@@ -0,0 +1,972 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addon\aikefu\api\controller;
|
||||||
|
|
||||||
|
use addon\aikefu\model\Config as KefuConfigModel;
|
||||||
|
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||||
|
use addon\aikefu\model\Message as KefuMessageModel;
|
||||||
|
use app\api\controller\WebSocketBase;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use think\facade\Db as Db;
|
||||||
|
use think\facade\Config;
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocket extends WebSocketBase
|
||||||
|
{
|
||||||
|
// 控制器属性,用于替代BaseApi中的属性
|
||||||
|
public $params;
|
||||||
|
public $token;
|
||||||
|
protected $member_id;
|
||||||
|
protected $site_id;
|
||||||
|
protected $uniacid;
|
||||||
|
protected $site_ids = [];
|
||||||
|
public $app_type;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// 调用父类构造函数,传入当前addon名称
|
||||||
|
parent::__construct('aikefu');
|
||||||
|
|
||||||
|
// 初始化控制器属性
|
||||||
|
$this->params = [];
|
||||||
|
$this->token = '';
|
||||||
|
$this->member_id = 0;
|
||||||
|
$this->site_id = 0;
|
||||||
|
$this->uniacid = 0;
|
||||||
|
$this->app_type = 'weapp'; // 默认微信小程序
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当有新客户端连接时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn)
|
||||||
|
{
|
||||||
|
// 存储新连接的客户端
|
||||||
|
$this->clients->attach($conn);
|
||||||
|
$this->clientData[$conn->resourceId] = [
|
||||||
|
'connection' => $conn,
|
||||||
|
'site_id' => null,
|
||||||
|
'member_id' => null,
|
||||||
|
'token' => null,
|
||||||
|
'is_authenticated' => false,
|
||||||
|
'conversation_id' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
echo "New connection! ({$conn->resourceId})
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当从客户端收到消息时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $conn, $message)
|
||||||
|
{
|
||||||
|
$numRecv = count($this->clients) - 1;
|
||||||
|
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n",
|
||||||
|
$conn->resourceId, $message, $numRecv, $numRecv == 1 ? '' : 's');
|
||||||
|
|
||||||
|
// 解析消息
|
||||||
|
try {
|
||||||
|
$data = json_decode($message, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new \Exception('Invalid JSON format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理认证
|
||||||
|
if (isset($data['action']) && $data['action'] === 'auth') {
|
||||||
|
$this->handleAuth($conn, $data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已认证
|
||||||
|
if (!$this->clientData[$conn->resourceId]['is_authenticated']) {
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => 'Not authenticated']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理聊天消息
|
||||||
|
if (isset($data['action']) && $data['action'] === 'chat') {
|
||||||
|
$this->handleChat($conn, $data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理心跳
|
||||||
|
if (isset($data['action']) && $data['action'] === 'ping') {
|
||||||
|
$conn->send(json_encode(['type' => 'pong']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => 'Unknown action']));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => $e->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当客户端连接关闭时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn)
|
||||||
|
{
|
||||||
|
// 移除连接
|
||||||
|
$this->clients->detach($conn);
|
||||||
|
unset($this->clientData[$conn->resourceId]);
|
||||||
|
|
||||||
|
echo "Connection {$conn->resourceId} has disconnected\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当连接发生错误时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param \Exception $e
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e)
|
||||||
|
{
|
||||||
|
echo "An error has occurred: {$e->getMessage()}\n";
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理客户端认证
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
protected function handleAuth(ConnectionInterface $conn, $data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$site_id = $data['site_id'] ?? null;
|
||||||
|
$member_id = $data['member_id'] ?? null;
|
||||||
|
$token = $data['token'] ?? null;
|
||||||
|
|
||||||
|
if (empty($site_id) || empty($member_id) || empty($token)) {
|
||||||
|
throw new \Exception('Missing authentication parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以添加更严格的认证逻辑,例如验证token的有效性
|
||||||
|
// 为了简单起见,我们暂时只检查参数是否存在
|
||||||
|
|
||||||
|
$this->clientData[$conn->resourceId]['site_id'] = $site_id;
|
||||||
|
$this->clientData[$conn->resourceId]['member_id'] = $member_id;
|
||||||
|
$this->clientData[$conn->resourceId]['token'] = $token;
|
||||||
|
$this->clientData[$conn->resourceId]['is_authenticated'] = true;
|
||||||
|
|
||||||
|
$conn->send(json_encode(['type' => 'auth_success', 'message' => 'Authenticated successfully']));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$conn->send(json_encode(['type' => 'auth_error', 'message' => $e->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理聊天消息
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
protected function handleChat(ConnectionInterface $conn, $data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$clientInfo = $this->clientData[$conn->resourceId];
|
||||||
|
|
||||||
|
// 获取请求参数
|
||||||
|
$message = $data['message'] ?? '';
|
||||||
|
$user_id = $data['user_id'] ?? $clientInfo['member_id'];
|
||||||
|
$conversation_id = $data['conversation_id'] ?? '';
|
||||||
|
$stream = $data['stream'] ?? true; // WebSocket默认使用流式响应
|
||||||
|
|
||||||
|
// 设置当前控制器的属性
|
||||||
|
$this->site_id = $clientInfo['site_id'];
|
||||||
|
$this->member_id = $clientInfo['member_id'];
|
||||||
|
$this->token = $clientInfo['token'];
|
||||||
|
$this->params = [
|
||||||
|
'message' => $message,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'stream' => $stream,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 验证参数并获取配置
|
||||||
|
$config = $this->validateAndGetConfig([
|
||||||
|
'message' => ['required' => true, 'message' => '请求参数 `message` 不能为空. 为消息内容', 'description' => '消息内容'],
|
||||||
|
'user_id' => ['required' => true, 'message' => '请求参数 `user_id` 不能为空', 'description' => '用户ID']
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 构建请求数据和请求头
|
||||||
|
$requestData = $this->buildRequestData($message, $user_id, $conversation_id, $stream);
|
||||||
|
$headers = $this->buildRequestHeaders($config['api_key']);
|
||||||
|
|
||||||
|
// 发送请求到Dify API
|
||||||
|
$url = $config['base_url'] . $config['chat_endpoint'];
|
||||||
|
|
||||||
|
if ($stream) {
|
||||||
|
// 处理流式响应
|
||||||
|
$this->handleStreamingResponse($conn, $url, $requestData, $headers, $message, $user_id);
|
||||||
|
} else {
|
||||||
|
// 处理非流式响应
|
||||||
|
$response = $this->handleBlockingResponse($url, $requestData, $headers, $message, $user_id, $conversation_id);
|
||||||
|
$conn->send(json_encode(['type' => 'message', 'data' => $response]));
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => '请求失败:' . $e->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理流式响应
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param string $url
|
||||||
|
* @param array $requestData
|
||||||
|
* @param array $headers
|
||||||
|
* @param string $message
|
||||||
|
* @param string $user_id
|
||||||
|
*/
|
||||||
|
private function handleStreamingResponse(ConnectionInterface $conn, $url, $requestData, $headers, $message, $user_id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 记录开始处理流式请求
|
||||||
|
$this->log('AI客服WebSocket流式请求开始处理,用户ID:' . $user_id . ',请求消息:' . $message, 'info');
|
||||||
|
|
||||||
|
// 初始化模型
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
$site_id = $this->site_id;
|
||||||
|
$current_user_id = $this->member_id;
|
||||||
|
|
||||||
|
// 定义变量
|
||||||
|
$real_conversation_id = '';
|
||||||
|
$real_assistant_message_id = '';
|
||||||
|
$real_user_message_id = '';
|
||||||
|
$assistant_content = '';
|
||||||
|
$user_message_saved = false;
|
||||||
|
$user_message_content = $message;
|
||||||
|
$temp_conversation_id = 'temp_' . uniqid() . '_' . time(); // 临时会话ID,用于失败回滚
|
||||||
|
|
||||||
|
// 立即保存用户消息,使用临时会话ID
|
||||||
|
$this->saveUserMessage($kefu_message_model, $site_id, $current_user_id, $temp_conversation_id, '', $user_message_content);
|
||||||
|
$this->log('用户消息已立即保存,临时会话ID:' . $temp_conversation_id, 'info');
|
||||||
|
|
||||||
|
// 创建或更新临时会话
|
||||||
|
$this->updateOrCreateConversation($kefu_conversation_model, $site_id, $current_user_id, $temp_conversation_id);
|
||||||
|
$this->log('临时会话已创建,ID:' . $temp_conversation_id, 'info');
|
||||||
|
|
||||||
|
// WebSocket消息回调
|
||||||
|
$on_data = function ($data) use (
|
||||||
|
$conn,
|
||||||
|
&$real_conversation_id,
|
||||||
|
&$real_assistant_message_id,
|
||||||
|
&$real_user_message_id,
|
||||||
|
&$assistant_content,
|
||||||
|
&$user_message_saved,
|
||||||
|
$user_message_content,
|
||||||
|
$kefu_message_model,
|
||||||
|
$kefu_conversation_model,
|
||||||
|
$site_id,
|
||||||
|
$current_user_id,
|
||||||
|
$temp_conversation_id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 解析Dify的流式响应
|
||||||
|
$lines = explode("\n", $data);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
if (empty($line))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 查找以"data: "开头的行
|
||||||
|
if (strpos($line, 'data: ') === 0) {
|
||||||
|
$json_data = substr($line, 6);
|
||||||
|
$event_data = json_decode($json_data, true);
|
||||||
|
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && isset($event_data['event'])) {
|
||||||
|
$event = $event_data['event'];
|
||||||
|
$this->log('处理AI客服事件:' . $event, 'info');
|
||||||
|
|
||||||
|
switch ($event_data['event']) {
|
||||||
|
case 'message':
|
||||||
|
// LLM返回文本块事件
|
||||||
|
if (isset($event_data['conversation_id'])) {
|
||||||
|
$real_conversation_id = $event_data['conversation_id'];
|
||||||
|
$this->log('获取到会话ID:' . $real_conversation_id, 'info');
|
||||||
|
}
|
||||||
|
if (isset($event_data['message_id'])) {
|
||||||
|
$real_assistant_message_id = $event_data['message_id'];
|
||||||
|
$this->log('获取到助手消息ID:' . $real_assistant_message_id, 'info');
|
||||||
|
}
|
||||||
|
// 积累助手回复内容
|
||||||
|
if (isset($event_data['answer'])) {
|
||||||
|
$assistant_content .= $event_data['answer'];
|
||||||
|
$this->log('积累助手回复内容:' . $event_data['answer'], 'debug');
|
||||||
|
|
||||||
|
// 通过WebSocket发送消息
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'message',
|
||||||
|
'conversation_id' => $real_conversation_id,
|
||||||
|
'message_id' => $real_assistant_message_id,
|
||||||
|
'answer' => $event_data['answer'],
|
||||||
|
'full_content' => $assistant_content
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 实时保存助手回复内容(流式过程中)
|
||||||
|
if (!empty($real_conversation_id) && !empty($real_assistant_message_id)) {
|
||||||
|
$this->saveStreamingAssistantMessage($kefu_message_model, $site_id, $current_user_id, $real_conversation_id, $real_assistant_message_id, $assistant_content, 'streaming');
|
||||||
|
$this->log('助手回复内容已实时保存,字数:' . strlen($assistant_content), 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新用户消息的会话ID(仅在第一次获取到真实会话ID时)
|
||||||
|
if (!$user_message_saved && !empty($real_conversation_id)) {
|
||||||
|
// 将临时会话ID更新为真实会话ID
|
||||||
|
$kefu_message_model->updateMessage(['conversation_id' => $real_conversation_id], [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $current_user_id],
|
||||||
|
['conversation_id', '=', $temp_conversation_id],
|
||||||
|
['role', '=', 'user']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$kefu_conversation_model->updateConversation(['conversation_id' => $real_conversation_id], [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $current_user_id],
|
||||||
|
['conversation_id', '=', $temp_conversation_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user_message_saved = true;
|
||||||
|
$this->log('用户消息会话ID已更新为真实ID:' . $real_conversation_id, 'info');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'agent_message':
|
||||||
|
// Agent模式下返回文本块事件
|
||||||
|
if (isset($event_data['conversation_id'])) {
|
||||||
|
$real_conversation_id = $event_data['conversation_id'];
|
||||||
|
$this->log('获取到Agent模式会话ID:' . $real_conversation_id, 'info');
|
||||||
|
}
|
||||||
|
if (isset($event_data['message_id'])) {
|
||||||
|
$real_assistant_message_id = $event_data['message_id'];
|
||||||
|
$this->log('获取到Agent模式助手消息ID:' . $real_assistant_message_id, 'info');
|
||||||
|
}
|
||||||
|
// 积累助手回复内容
|
||||||
|
if (isset($event_data['answer'])) {
|
||||||
|
$assistant_content .= $event_data['answer'];
|
||||||
|
$this->log('积累Agent回复内容:' . $event_data['answer'], 'debug');
|
||||||
|
|
||||||
|
// 通过WebSocket发送消息
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'agent_message',
|
||||||
|
'conversation_id' => $real_conversation_id,
|
||||||
|
'message_id' => $real_assistant_message_id,
|
||||||
|
'answer' => $event_data['answer'],
|
||||||
|
'full_content' => $assistant_content
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 实时保存助手回复内容(Agent模式流式过程中)
|
||||||
|
if (!empty($real_conversation_id) && !empty($real_assistant_message_id)) {
|
||||||
|
$this->saveStreamingAssistantMessage($kefu_message_model, $site_id, $current_user_id, $real_conversation_id, $real_assistant_message_id, $assistant_content, 'streaming');
|
||||||
|
$this->log('Agent模式助手回复内容已实时保存,字数:' . strlen($assistant_content), 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新用户消息的会话ID(仅在第一次获取到真实会话ID时)
|
||||||
|
if (!$user_message_saved && !empty($real_conversation_id)) {
|
||||||
|
// 将临时会话ID更新为真实会话ID
|
||||||
|
$kefu_message_model->updateMessage(['conversation_id' => $real_conversation_id], [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $current_user_id],
|
||||||
|
['conversation_id', '=', $temp_conversation_id],
|
||||||
|
['role', '=', 'user']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$kefu_conversation_model->updateConversation(['conversation_id' => $real_conversation_id], [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $current_user_id],
|
||||||
|
['conversation_id', '=', $temp_conversation_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user_message_saved = true;
|
||||||
|
$this->log('Agent模式下用户消息会话ID已更新为真实ID:' . $real_conversation_id, 'info');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'agent_thought':
|
||||||
|
if (isset($event_data['thought'])) {
|
||||||
|
// 格式化思考过程
|
||||||
|
$thought_content = "\n[思考过程]: " . $event_data['thought'];
|
||||||
|
if (isset($event_data['tool'])) {
|
||||||
|
$thought_content .= "\n[使用工具]: " . $event_data['tool'];
|
||||||
|
}
|
||||||
|
if (isset($event_data['observation'])) {
|
||||||
|
$thought_content .= "\n[观察结果]: " . $event_data['observation'];
|
||||||
|
}
|
||||||
|
$assistant_content .= $thought_content;
|
||||||
|
$this->log('Agent思考过程:' . $thought_content, 'debug');
|
||||||
|
|
||||||
|
// 通过WebSocket发送思考过程
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'agent_thought',
|
||||||
|
'thought' => $event_data['thought'],
|
||||||
|
'tool' => $event_data['tool'] ?? null,
|
||||||
|
'observation' => $event_data['observation'] ?? null
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'file':
|
||||||
|
if (isset($event_data['id']) && isset($event_data['type']) && isset($event_data['url'])) {
|
||||||
|
$file_id = $event_data['id'];
|
||||||
|
$file_type = $event_data['type'];
|
||||||
|
$file_url = $event_data['url'];
|
||||||
|
// 可以将文件信息作为特殊内容添加到回复中
|
||||||
|
$file_content = "\n[文件]: " . $file_type . " - " . $file_url;
|
||||||
|
$assistant_content .= $file_content;
|
||||||
|
$this->log('收到文件事件:' . $file_type . ' - ' . $file_url, 'info');
|
||||||
|
|
||||||
|
// 通过WebSocket发送文件信息
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'file',
|
||||||
|
'id' => $file_id,
|
||||||
|
'type' => $file_type,
|
||||||
|
'url' => $file_url
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message_start':
|
||||||
|
if (isset($event_data['conversation_id'])) {
|
||||||
|
$real_conversation_id = $event_data['conversation_id'];
|
||||||
|
$this->log('消息开始事件,会话ID:' . $real_conversation_id, 'info');
|
||||||
|
|
||||||
|
// 通过WebSocket发送消息开始事件
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'message_start',
|
||||||
|
'conversation_id' => $real_conversation_id
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message_delta':
|
||||||
|
if (isset($event_data['delta']['content'])) {
|
||||||
|
$assistant_content .= $event_data['delta']['content'];
|
||||||
|
$this->log('积累增量内容:' . $event_data['delta']['content'], 'debug');
|
||||||
|
|
||||||
|
// 通过WebSocket发送增量内容
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'message_delta',
|
||||||
|
'delta' => $event_data['delta'],
|
||||||
|
'full_content' => $assistant_content
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 实时保存助手回复内容(增量流式过程中)
|
||||||
|
if (!empty($real_conversation_id) && !empty($real_assistant_message_id)) {
|
||||||
|
$this->saveStreamingAssistantMessage($kefu_message_model, $site_id, $current_user_id, $real_conversation_id, $real_assistant_message_id, $assistant_content, 'streaming');
|
||||||
|
$this->log('助手增量回复内容已实时保存,字数:' . strlen($assistant_content), 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message_end':
|
||||||
|
// 最终内容已通过message或message_delta事件积累
|
||||||
|
$this->log('消息结束事件,会话ID:' . $real_conversation_id . ',消息ID:' . $real_assistant_message_id, 'info');
|
||||||
|
|
||||||
|
// 通过WebSocket发送消息结束事件
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'message_end',
|
||||||
|
'conversation_id' => $real_conversation_id,
|
||||||
|
'message_id' => $real_assistant_message_id,
|
||||||
|
'content' => $assistant_content
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
$error_message = isset($event_data['message']) ? $event_data['message'] : '流式输出异常';
|
||||||
|
$assistant_content .= "\n[错误]: " . $error_message;
|
||||||
|
$this->log('AI客服错误事件:' . $error_message, 'error');
|
||||||
|
|
||||||
|
// 通过WebSocket发送错误事件
|
||||||
|
$conn->send(json_encode([
|
||||||
|
'type' => 'message',
|
||||||
|
'event' => 'error',
|
||||||
|
'message' => $error_message
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ping':
|
||||||
|
// 保持连接存活的ping事件
|
||||||
|
// 无需特殊处理,继续保持连接
|
||||||
|
$this->log('收到ping事件', 'debug');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->log('AI客服事件解析失败:' . $json_data, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->log('AI客服事件处理异常:' . $e->getMessage(), 'error');
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => $e->getMessage()]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 错误处理回调函数
|
||||||
|
$on_error = function ($error) use ($user_id, $conn) {
|
||||||
|
$this->log('AI客服请求错误,用户ID:' . $user_id . ',错误信息:' . json_encode($error), 'error');
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => $error]));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用curl流式请求
|
||||||
|
$this->curlRequestStreaming($url, 'POST', $requestData, $headers, $on_data, $on_error);
|
||||||
|
$this->log('AI客服请求成功,用户ID:' . $user_id . ',会话ID:' . $real_conversation_id, 'info');
|
||||||
|
|
||||||
|
// 数据流结束时发送明确的"done"事件
|
||||||
|
$done_data = [
|
||||||
|
'conversation_id' => $real_conversation_id,
|
||||||
|
'message_id' => $real_assistant_message_id,
|
||||||
|
'content' => $assistant_content,
|
||||||
|
];
|
||||||
|
$conn->send(json_encode(['type' => 'message', 'event' => 'done', 'data' => $done_data]));
|
||||||
|
|
||||||
|
// 流式正常完成,标记助手消息为已完成状态
|
||||||
|
if (!empty($real_conversation_id) && !empty($real_assistant_message_id) && !empty($assistant_content)) {
|
||||||
|
$this->saveStreamingAssistantMessage($kefu_message_model, $site_id, $current_user_id, $real_conversation_id, $real_assistant_message_id, $assistant_content, 'completed');
|
||||||
|
$this->log('AI客服回复已标记为完成状态,会话ID:' . $real_conversation_id . ',总字数:' . strlen($assistant_content), 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理临时数据
|
||||||
|
$this->cleanupTempData($kefu_message_model, $kefu_conversation_model, $site_id, $current_user_id, $temp_conversation_id);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$error_msg = 'AI客服请求异常:' . $e->getMessage() . ',错误行:' . $e->getLine() . ',错误文件:' . $e->getFile();
|
||||||
|
$this->log($error_msg, 'error');
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => $error_msg]));
|
||||||
|
|
||||||
|
// 异常时清理临时数据
|
||||||
|
try {
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
$site_id = $this->site_id;
|
||||||
|
$current_user_id = $this->member_id;
|
||||||
|
$this->cleanupTempData($kefu_message_model, $kefu_conversation_model, $site_id, $current_user_id, $temp_conversation_id);
|
||||||
|
} catch (\Exception $cleanupException) {
|
||||||
|
$this->log('清理临时数据时也发生异常:' . $cleanupException->getMessage(), 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的curl流式请求函数
|
||||||
|
* @param string $url 请求URL
|
||||||
|
* @param string $method 请求方法
|
||||||
|
* @param array $data 请求数据
|
||||||
|
* @param array $headers 请求头
|
||||||
|
* @param callable|null $on_data 数据回调函数,接收原始数据
|
||||||
|
* @param callable|null $on_error 错误回调函数,接收错误信息
|
||||||
|
* @return bool 请求是否成功
|
||||||
|
*/
|
||||||
|
private function curlRequestStreaming($url, $method = 'GET', $data = [], $headers = [], $on_data = null, $on_error = null)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
// 设置URL
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|
||||||
|
// 设置请求方法
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
|
|
||||||
|
// 设置POST数据
|
||||||
|
if ($method === 'POST' && !empty($data)) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? json_encode($data) : $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
if (!empty($headers)) {
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
} else {
|
||||||
|
// 默认请求头
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置cURL选项以支持流式输出
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 不返回结果,直接输出
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 0); // 无超时限制,适用于长时间流式响应
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
|
||||||
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($curl, $data) use ($on_data) {
|
||||||
|
// 调用自定义数据处理回调
|
||||||
|
if (is_callable($on_data)) {
|
||||||
|
$on_data($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行请求并流式输出响应
|
||||||
|
curl_exec($ch);
|
||||||
|
|
||||||
|
if (curl_errno($ch)) {
|
||||||
|
$error = curl_error($ch);
|
||||||
|
$this->log('Curl请求错误:' . $error, 'error');
|
||||||
|
|
||||||
|
if (is_callable($on_error)) {
|
||||||
|
$on_error($error);
|
||||||
|
}
|
||||||
|
curl_close($ch);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
return true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->log(json_encode(["event" => "error", "data" => $e->getMessage(), "line" => $e->getLine(), "file" => $e->getFile()]), 'error');
|
||||||
|
if (is_callable($on_error)) {
|
||||||
|
$on_error($e->getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装curl请求方法
|
||||||
|
* @param string $url 请求URL
|
||||||
|
* @param string $method 请求方法
|
||||||
|
* @param array $data 请求数据
|
||||||
|
* @param array $headers 请求头
|
||||||
|
* @return string 响应内容
|
||||||
|
*/
|
||||||
|
private function curlRequest($url, $method = 'GET', $data = [], $headers = [])
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
// 设置URL
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|
||||||
|
// 设置请求方法
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
|
|
||||||
|
// 设置POST数据
|
||||||
|
if ($method === 'POST' && !empty($data)) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? json_encode($data) : $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
if (!empty($headers)) {
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置返回值
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
// 执行请求
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
// 关闭连接
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new \Exception('Curl请求失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode >= 400) {
|
||||||
|
throw new \Exception('HTTP请求失败,状态码:' . $httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证参数并获取配置
|
||||||
|
* @param array $params_rules 参数验证规则
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function validateAndGetConfig($params_rules = [])
|
||||||
|
{
|
||||||
|
// 参数验证规则
|
||||||
|
$rules = [];
|
||||||
|
|
||||||
|
// 合并参数验证规则
|
||||||
|
$rules = array_merge($rules, $params_rules);
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
foreach ($rules as $field => $rule) {
|
||||||
|
if (isset($rule['required']) && $rule['required'] && empty($this->params[$field])) {
|
||||||
|
throw new \Exception($rule['message']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取智能客服配置
|
||||||
|
$kefu_config_model = new KefuConfigModel();
|
||||||
|
$config_info = $kefu_config_model->getConfig($this->site_id)['data']['value'] ?? [];
|
||||||
|
|
||||||
|
if (empty($config_info) || $config_info['status'] != 1) {
|
||||||
|
throw new \Exception('智能客服暂未启用');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建请求数据
|
||||||
|
* @param string $message 用户消息
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $conversation_id 会话ID
|
||||||
|
* @param bool $stream 是否使用流式响应
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function buildRequestData($message, $user_id, $conversation_id, $stream)
|
||||||
|
{
|
||||||
|
$requestData = [
|
||||||
|
'inputs' => [],
|
||||||
|
'query' => $message,
|
||||||
|
'response_mode' => $stream ? 'streaming' : 'blocking',
|
||||||
|
'user' => $user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 如果有会话ID,添加到请求中
|
||||||
|
if (!empty($conversation_id)) {
|
||||||
|
$requestData['conversation_id'] = $conversation_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $requestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建请求头
|
||||||
|
* @param string $api_key API密钥
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function buildRequestHeaders($api_key)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Authorization: Bearer ' . $api_key,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存用户消息
|
||||||
|
* @param KefuMessageModel $message_model 消息模型
|
||||||
|
* @param string $site_id 站点ID
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $conversation_id 会话ID
|
||||||
|
* @param string $message_id 消息ID
|
||||||
|
* @param string $content 消息内容
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function saveUserMessage($message_model, $site_id, $user_id, $conversation_id, $message_id, $content)
|
||||||
|
{
|
||||||
|
$message_model->addMessage([
|
||||||
|
'site_id' => $site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'role' => 'user',
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存助手消息
|
||||||
|
* @param KefuMessageModel $message_model 消息模型
|
||||||
|
* @param string $site_id 站点ID
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $conversation_id 会话ID
|
||||||
|
* @param string $message_id 消息ID
|
||||||
|
* @param string $content 消息内容
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function saveAssistantMessage($message_model, $site_id, $user_id, $conversation_id, $message_id, $content)
|
||||||
|
{
|
||||||
|
$message_model->addMessage([
|
||||||
|
'site_id' => $site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'role' => 'assistant',
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新或创建会话
|
||||||
|
* @param KefuConversationModel $conversation_model 会话模型
|
||||||
|
* @param string $site_id 站点ID
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $conversation_id 会话ID
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function updateOrCreateConversation($conversation_model, $site_id, $user_id, $conversation_id)
|
||||||
|
{
|
||||||
|
$conversation_info = $conversation_model->getConversationInfo([
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id],
|
||||||
|
['conversation_id', '=', $conversation_id],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (empty($conversation_info['data'])) {
|
||||||
|
// 创建新会话
|
||||||
|
$conversation_model->addConversation([
|
||||||
|
'site_id' => $site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'name' => '智能客服会话',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// 更新会话状态
|
||||||
|
$conversation_model->updateConversation([
|
||||||
|
'status' => 1,
|
||||||
|
'update_time' => date('Y-m-d H:i:s')
|
||||||
|
], [
|
||||||
|
['id', '=', $conversation_info['data']['id']],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时保存助手消息内容(流式过程中)
|
||||||
|
* @param KefuMessageModel $message_model 消息模型
|
||||||
|
* @param string $site_id 站点ID
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $conversation_id 会话ID
|
||||||
|
* @param string $message_id 消息ID
|
||||||
|
* @param string $content 消息内容
|
||||||
|
* @param string $status 消息状态:streaming(流式中)、completed(已完成)
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function saveStreamingAssistantMessage($message_model, $site_id, $user_id, $conversation_id, $message_id, $content, $status = 'streaming')
|
||||||
|
{
|
||||||
|
// 检查是否已存在该消息(用于更新)
|
||||||
|
$existing_message = $message_model->getMessageInfo([
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id],
|
||||||
|
['conversation_id', '=', $conversation_id],
|
||||||
|
['message_id', '=', $message_id],
|
||||||
|
['role', '=', 'assistant']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$message_data = [
|
||||||
|
'site_id' => $site_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'role' => 'assistant',
|
||||||
|
'content' => $content,
|
||||||
|
'status' => $status, // 新增状态字段
|
||||||
|
'update_time' => date('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
|
||||||
|
if (empty($existing_message['data'])) {
|
||||||
|
// 新增消息
|
||||||
|
$message_model->addMessage($message_data);
|
||||||
|
} else {
|
||||||
|
// 更新消息内容
|
||||||
|
$message_model->updateMessage($message_data, [
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id],
|
||||||
|
['conversation_id', '=', $conversation_id],
|
||||||
|
['message_id', '=', $message_id],
|
||||||
|
['role', '=', 'assistant']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理临时消息和会话
|
||||||
|
* @param KefuMessageModel $message_model 消息模型
|
||||||
|
* @param KefuConversationModel $conversation_model 会话模型
|
||||||
|
* @param string $site_id 站点ID
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $temp_conversation_id 临时会话ID
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function cleanupTempData($message_model, $conversation_model, $site_id, $user_id, $temp_conversation_id)
|
||||||
|
{
|
||||||
|
// 删除临时消息
|
||||||
|
$message_model->deleteMessage([
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id],
|
||||||
|
['conversation_id', '=', $temp_conversation_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 删除临时会话
|
||||||
|
$conversation_model->deleteConversation([
|
||||||
|
['site_id', '=', $site_id],
|
||||||
|
['user_id', '=', $user_id],
|
||||||
|
['conversation_id', '=', $temp_conversation_id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log('临时数据已清理,会话ID:' . $temp_conversation_id, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志记录封装方法
|
||||||
|
* @param string $message 日志内容
|
||||||
|
* @param string $level 日志级别,默认为info
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function log($message, $level = 'info')
|
||||||
|
{
|
||||||
|
// 只允许info、error级别
|
||||||
|
if (!in_array($level, ['info', 'error'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log_write($message, $level, '', 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理非流式响应
|
||||||
|
* @param string $url 请求URL
|
||||||
|
* @param array $requestData 请求数据
|
||||||
|
* @param array $headers 请求头
|
||||||
|
* @param string $message 用户消息
|
||||||
|
* @param string $user_id 用户ID
|
||||||
|
* @param string $conversation_id 会话ID
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function handleBlockingResponse($url, $requestData, $headers, $message, $user_id, $conversation_id)
|
||||||
|
{
|
||||||
|
// 发送请求
|
||||||
|
$response = $this->curlRequest($url, 'POST', $requestData, $headers);
|
||||||
|
$response_data = json_decode($response, true);
|
||||||
|
|
||||||
|
// 初始化模型
|
||||||
|
$kefu_conversation_model = new KefuConversationModel();
|
||||||
|
$kefu_message_model = new KefuMessageModel();
|
||||||
|
$site_id = $this->site_id;
|
||||||
|
$current_user_id = $this->member_id;
|
||||||
|
|
||||||
|
// 保存用户消息
|
||||||
|
$this->saveUserMessage($kefu_message_model, $site_id, $current_user_id, $conversation_id, '', $message);
|
||||||
|
|
||||||
|
// 更新或创建会话
|
||||||
|
$real_conversation_id = $response_data['conversation_id'] ?? $conversation_id;
|
||||||
|
$this->updateOrCreateConversation($kefu_conversation_model, $site_id, $current_user_id, $real_conversation_id);
|
||||||
|
|
||||||
|
// 保存助手消息
|
||||||
|
$assistant_content = $response_data['answer'] ?? '';
|
||||||
|
$real_assistant_message_id = $response_data['message_id'] ?? '';
|
||||||
|
$this->saveAssistantMessage($kefu_message_model, $site_id, $current_user_id, $real_conversation_id, $real_assistant_message_id, $assistant_content);
|
||||||
|
|
||||||
|
// 返回响应
|
||||||
|
return [
|
||||||
|
'conversation_id' => $real_conversation_id,
|
||||||
|
'message_id' => $real_assistant_message_id,
|
||||||
|
'content' => $assistant_content,
|
||||||
|
'answer' => $assistant_content,
|
||||||
|
'status' => 'completed'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
223
src/addon/aikefu/docs/ws_multi_addon_test.html
Normal file
223
src/addon/aikefu/docs/ws_multi_addon_test.html
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebSocket多addon测试</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.addon-test {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.addon-test h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.chat-area {
|
||||||
|
height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.message.user {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.message.server {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
input[type="text"] {
|
||||||
|
width: 70%;
|
||||||
|
padding: 8px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>WebSocket多addon测试</h1>
|
||||||
|
|
||||||
|
<!-- aikefu addon 测试 -->
|
||||||
|
<div class="addon-test">
|
||||||
|
<h2>aikefu Addon (ws://localhost:8080/ws/aikefu)</h2>
|
||||||
|
<div class="status" id="status-aikefu">未连接</div>
|
||||||
|
<div class="chat-area" id="chat-aikefu"></div>
|
||||||
|
<div>
|
||||||
|
<input type="text" id="message-aikefu" placeholder="输入消息...">
|
||||||
|
<button onclick="sendMessage('aikefu')">发送</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 默认路径测试 -->
|
||||||
|
<div class="addon-test">
|
||||||
|
<h2>默认路径 (ws://localhost:8080/ws)</h2>
|
||||||
|
<div class="status" id="status-default">未连接</div>
|
||||||
|
<div class="chat-area" id="chat-default"></div>
|
||||||
|
<div>
|
||||||
|
<input type="text" id="message-default" placeholder="输入消息...">
|
||||||
|
<button onclick="sendMessage('default')">发送</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// WebSocket连接对象
|
||||||
|
let wsConnections = {};
|
||||||
|
|
||||||
|
// 连接配置
|
||||||
|
const configs = {
|
||||||
|
aikefu: {
|
||||||
|
url: 'ws://localhost:8080/ws/aikefu',
|
||||||
|
status: document.getElementById('status-aikefu'),
|
||||||
|
chat: document.getElementById('chat-aikefu'),
|
||||||
|
message: document.getElementById('message-aikefu')
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
url: 'ws://localhost:8080/ws',
|
||||||
|
status: document.getElementById('status-default'),
|
||||||
|
chat: document.getElementById('chat-default'),
|
||||||
|
message: document.getElementById('message-default')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化所有连接
|
||||||
|
function initConnections() {
|
||||||
|
for (const name in configs) {
|
||||||
|
connect(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接到WebSocket服务器
|
||||||
|
function connect(name) {
|
||||||
|
const config = configs[name];
|
||||||
|
|
||||||
|
try {
|
||||||
|
wsConnections[name] = new WebSocket(config.url);
|
||||||
|
|
||||||
|
wsConnections[name].onopen = function() {
|
||||||
|
config.status.textContent = '已连接';
|
||||||
|
config.status.style.color = 'green';
|
||||||
|
|
||||||
|
// 发送认证信息
|
||||||
|
const authMsg = JSON.stringify({
|
||||||
|
action: 'auth',
|
||||||
|
site_id: 1,
|
||||||
|
member_id: 1,
|
||||||
|
token: 'test_token'
|
||||||
|
});
|
||||||
|
wsConnections[name].send(authMsg);
|
||||||
|
|
||||||
|
addMessage(name, '系统', '已连接到WebSocket服务器');
|
||||||
|
};
|
||||||
|
|
||||||
|
wsConnections[name].onmessage = function(event) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
addMessage(name, '服务器', JSON.stringify(data, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
addMessage(name, '服务器', event.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
wsConnections[name].onclose = function() {
|
||||||
|
config.status.textContent = '已断开';
|
||||||
|
config.status.style.color = 'red';
|
||||||
|
addMessage(name, '系统', 'WebSocket连接已断开');
|
||||||
|
};
|
||||||
|
|
||||||
|
wsConnections[name].onerror = function(error) {
|
||||||
|
config.status.textContent = '连接错误';
|
||||||
|
config.status.style.color = 'red';
|
||||||
|
addMessage(name, '系统', 'WebSocket连接错误: ' + error);
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
config.status.textContent = '连接失败';
|
||||||
|
config.status.style.color = 'red';
|
||||||
|
addMessage(name, '系统', '无法连接到WebSocket服务器: ' + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
function sendMessage(name) {
|
||||||
|
const config = configs[name];
|
||||||
|
const message = config.message.value.trim();
|
||||||
|
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
addMessage(name, '用户', message);
|
||||||
|
|
||||||
|
// 发送聊天消息
|
||||||
|
const chatMsg = JSON.stringify({
|
||||||
|
action: 'chat',
|
||||||
|
message: message,
|
||||||
|
user_id: 1,
|
||||||
|
stream: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wsConnections[name] && wsConnections[name].readyState === WebSocket.OPEN) {
|
||||||
|
wsConnections[name].send(chatMsg);
|
||||||
|
config.message.value = '';
|
||||||
|
} else {
|
||||||
|
addMessage(name, '系统', 'WebSocket未连接,无法发送消息');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加消息到聊天区域
|
||||||
|
function addMessage(name, sender, message) {
|
||||||
|
const config = configs[name];
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'message ' + (sender === '用户' ? 'user' : 'server');
|
||||||
|
div.innerHTML = `<strong>${sender}:</strong><br>${message}`;
|
||||||
|
config.chat.appendChild(div);
|
||||||
|
config.chat.scrollTop = config.chat.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后初始化连接
|
||||||
|
window.onload = function() {
|
||||||
|
initConnections();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面关闭前关闭所有连接
|
||||||
|
window.onbeforeunload = function() {
|
||||||
|
for (const name in wsConnections) {
|
||||||
|
if (wsConnections[name] && wsConnections[name].readyState === WebSocket.OPEN) {
|
||||||
|
wsConnections[name].close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
207
src/app/api/controller/WebSocketBase.php
Normal file
207
src/app/api/controller/WebSocketBase.php
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket基类,用于各个addon继承实现自己的WebSocket控制器
|
||||||
|
* 提供统一的接口和基本功能,确保各个addon的WebSocket相互隔离
|
||||||
|
*/
|
||||||
|
abstract class WebSocketBase implements MessageComponentInterface
|
||||||
|
{
|
||||||
|
protected $clients;
|
||||||
|
protected $clientData;
|
||||||
|
protected $addonName;
|
||||||
|
|
||||||
|
// 控制器属性
|
||||||
|
public $params;
|
||||||
|
public $token;
|
||||||
|
protected $member_id;
|
||||||
|
protected $site_id;
|
||||||
|
protected $uniacid;
|
||||||
|
protected $site_ids = [];
|
||||||
|
public $app_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*/
|
||||||
|
public function __construct($addonName = '')
|
||||||
|
{
|
||||||
|
$this->clients = new \SplObjectStorage;
|
||||||
|
$this->clientData = [];
|
||||||
|
$this->addonName = $addonName;
|
||||||
|
|
||||||
|
// 初始化控制器属性
|
||||||
|
$this->params = [];
|
||||||
|
$this->token = '';
|
||||||
|
$this->member_id = 0;
|
||||||
|
$this->site_id = 0;
|
||||||
|
$this->uniacid = 0;
|
||||||
|
$this->app_type = 'weapp'; // 默认微信小程序
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当有新客户端连接时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn)
|
||||||
|
{
|
||||||
|
// 存储新连接的客户端
|
||||||
|
$this->clients->attach($conn);
|
||||||
|
$this->clientData[$conn->resourceId] = [
|
||||||
|
'connection' => $conn,
|
||||||
|
'site_id' => null,
|
||||||
|
'member_id' => null,
|
||||||
|
'token' => null,
|
||||||
|
'is_authenticated' => false,
|
||||||
|
'conversation_id' => null,
|
||||||
|
'addon_name' => $this->addonName, // 记录当前连接所属的addon
|
||||||
|
];
|
||||||
|
|
||||||
|
echo "[{$this->addonName}] New connection! ({$conn->resourceId})\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当从客户端收到消息时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $conn, $message)
|
||||||
|
{
|
||||||
|
$numRecv = count($this->clients) - 1;
|
||||||
|
echo sprintf('[{$this->addonName}] Connection %d sending message "%s" to %d other connection%s' . "\n",
|
||||||
|
$conn->resourceId, $message, $numRecv, $numRecv == 1 ? '' : 's');
|
||||||
|
|
||||||
|
// 解析消息
|
||||||
|
try {
|
||||||
|
$data = json_decode($message, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new \Exception('Invalid JSON format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理认证
|
||||||
|
if (isset($data['action']) && $data['action'] === 'auth') {
|
||||||
|
$this->handleAuth($conn, $data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已认证
|
||||||
|
if (!$this->clientData[$conn->resourceId]['is_authenticated']) {
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => 'Not authenticated']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理聊天消息
|
||||||
|
if (isset($data['action']) && $data['action'] === 'chat') {
|
||||||
|
$this->handleChat($conn, $data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理心跳
|
||||||
|
if (isset($data['action']) && $data['action'] === 'ping') {
|
||||||
|
$conn->send(json_encode(['type' => 'pong']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => 'Unknown action']));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$conn->send(json_encode(['type' => 'error', 'message' => $e->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当客户端连接关闭时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn)
|
||||||
|
{
|
||||||
|
// 移除连接
|
||||||
|
$this->clients->detach($conn);
|
||||||
|
unset($this->clientData[$conn->resourceId]);
|
||||||
|
|
||||||
|
echo "[{$this->addonName}] Connection {$conn->resourceId} has disconnected\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当连接发生错误时调用
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param \Exception $e
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e)
|
||||||
|
{
|
||||||
|
echo "[{$this->addonName}] An error has occurred: {$e->getMessage()}\n";
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理客户端认证
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
protected function handleAuth(ConnectionInterface $conn, $data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$site_id = $data['site_id'] ?? null;
|
||||||
|
$member_id = $data['member_id'] ?? null;
|
||||||
|
$token = $data['token'] ?? null;
|
||||||
|
|
||||||
|
if (empty($site_id) || empty($member_id) || empty($token)) {
|
||||||
|
throw new \Exception('Missing authentication parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子类可以重写此方法来实现更严格的认证逻辑
|
||||||
|
$this->doAuth($conn, $site_id, $member_id, $token);
|
||||||
|
|
||||||
|
$this->clientData[$conn->resourceId]['site_id'] = $site_id;
|
||||||
|
$this->clientData[$conn->resourceId]['member_id'] = $member_id;
|
||||||
|
$this->clientData[$conn->resourceId]['token'] = $token;
|
||||||
|
$this->clientData[$conn->resourceId]['is_authenticated'] = true;
|
||||||
|
|
||||||
|
$conn->send(json_encode(['type' => 'auth_success', 'message' => 'Authenticated successfully', 'addon' => $this->addonName]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$conn->send(json_encode(['type' => 'auth_error', 'message' => $e->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际的认证逻辑,子类可以重写此方法
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param int $site_id
|
||||||
|
* @param int $member_id
|
||||||
|
* @param string $token
|
||||||
|
*/
|
||||||
|
protected function doAuth(ConnectionInterface $conn, $site_id, $member_id, $token)
|
||||||
|
{
|
||||||
|
// 默认实现,子类应该重写此方法
|
||||||
|
// 这里可以添加更严格的认证逻辑,例如验证token的有效性
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理聊天消息
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
abstract protected function handleChat(ConnectionInterface $conn, $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息给指定客户端
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param array $message
|
||||||
|
*/
|
||||||
|
protected function sendMessage(ConnectionInterface $conn, $message)
|
||||||
|
{
|
||||||
|
$conn->send(json_encode(array_merge(['addon' => $this->addonName], $message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前addon名称
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAddonName()
|
||||||
|
{
|
||||||
|
return $this->addonName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,8 @@
|
|||||||
"setasign/fpdf": "^1.8",
|
"setasign/fpdf": "^1.8",
|
||||||
"setasign/fpdi": "^2.3",
|
"setasign/fpdi": "^2.3",
|
||||||
"qiniu/php-sdk": "^7.7",
|
"qiniu/php-sdk": "^7.7",
|
||||||
"ext-json": "*"
|
"ext-json": "*",
|
||||||
|
"cboden/ratchet": "^0.4.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/var-dumper": "4.4.41",
|
"symfony/var-dumper": "4.4.41",
|
||||||
|
|||||||
710
src/composer.lock
generated
710
src/composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "3a9b61ee22986f8c6ce073dbd33a8c1c",
|
"content-hash": "d8c75bcdbf0cba2315d8c2738c72d4d5",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aliyuncs/oss-sdk-php",
|
"name": "aliyuncs/oss-sdk-php",
|
||||||
@@ -51,6 +51,69 @@
|
|||||||
},
|
},
|
||||||
"time": "2024-10-28T10:41:12+00:00"
|
"time": "2024-10-28T10:41:12+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "cboden/ratchet",
|
||||||
|
"version": "v0.4.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ratchetphp/Ratchet.git",
|
||||||
|
"reference": "5012dc954541b40c5599d286fd40653f5716a38f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/5012dc954541b40c5599d286fd40653f5716a38f",
|
||||||
|
"reference": "5012dc954541b40c5599d286fd40653f5716a38f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"guzzlehttp/psr7": "^1.7|^2.0",
|
||||||
|
"php": ">=5.4.2",
|
||||||
|
"ratchet/rfc6455": "^0.3.1",
|
||||||
|
"react/event-loop": ">=0.4",
|
||||||
|
"react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5",
|
||||||
|
"symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0",
|
||||||
|
"symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~4.8"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ratchet\\": "src/Ratchet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Bonneau",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP WebSocket library",
|
||||||
|
"homepage": "http://socketo.me",
|
||||||
|
"keywords": [
|
||||||
|
"Ratchet",
|
||||||
|
"WebSockets",
|
||||||
|
"server",
|
||||||
|
"sockets",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://gitter.im/reactphp/reactphp",
|
||||||
|
"issues": "https://github.com/ratchetphp/Ratchet/issues",
|
||||||
|
"source": "https://github.com/ratchetphp/Ratchet/tree/v0.4.4"
|
||||||
|
},
|
||||||
|
"time": "2021-12-14T00:20:41+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dragonmantank/cron-expression",
|
"name": "dragonmantank/cron-expression",
|
||||||
"version": "v3.5.0",
|
"version": "v3.5.0",
|
||||||
@@ -163,6 +226,53 @@
|
|||||||
},
|
},
|
||||||
"time": "2021-07-05T04:03:22+00:00"
|
"time": "2021-07-05T04:03:22+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "evenement/evenement",
|
||||||
|
"version": "v3.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/igorw/evenement.git",
|
||||||
|
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
|
||||||
|
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9 || ^6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Evenement\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Igor Wiedler",
|
||||||
|
"email": "igor@wiedler.ch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Événement is a very simple event dispatching library for PHP",
|
||||||
|
"keywords": [
|
||||||
|
"event-dispatcher",
|
||||||
|
"event-emitter"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/igorw/evenement/issues",
|
||||||
|
"source": "https://github.com/igorw/evenement/tree/v3.0.2"
|
||||||
|
},
|
||||||
|
"time": "2023-08-08T05:53:35+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ezyang/htmlpurifier",
|
"name": "ezyang/htmlpurifier",
|
||||||
"version": "v4.19.0",
|
"version": "v4.19.0",
|
||||||
@@ -3074,6 +3184,514 @@
|
|||||||
},
|
},
|
||||||
"time": "2019-03-08T08:55:37+00:00"
|
"time": "2019-03-08T08:55:37+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ratchet/rfc6455",
|
||||||
|
"version": "v0.3.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ratchetphp/RFC6455.git",
|
||||||
|
"reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb",
|
||||||
|
"reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"guzzlehttp/psr7": "^2 || ^1.7",
|
||||||
|
"php": ">=5.4.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^5.7",
|
||||||
|
"react/socket": "^1.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ratchet\\RFC6455\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Bonneau",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "RFC6455 WebSocket protocol handler",
|
||||||
|
"homepage": "http://socketo.me",
|
||||||
|
"keywords": [
|
||||||
|
"WebSockets",
|
||||||
|
"rfc6455",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://gitter.im/reactphp/reactphp",
|
||||||
|
"issues": "https://github.com/ratchetphp/RFC6455/issues",
|
||||||
|
"source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1"
|
||||||
|
},
|
||||||
|
"time": "2021-12-09T23:20:49+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/cache",
|
||||||
|
"version": "v1.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/cache.git",
|
||||||
|
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
|
||||||
|
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"react/promise": "^3.0 || ^2.0 || ^1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Cache\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Async, Promise-based cache interface for ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"cache",
|
||||||
|
"caching",
|
||||||
|
"promise",
|
||||||
|
"reactphp"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/cache/issues",
|
||||||
|
"source": "https://github.com/reactphp/cache/tree/v1.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-11-30T15:59:55+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/dns",
|
||||||
|
"version": "v1.14.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/dns.git",
|
||||||
|
"reference": "7562c05391f42701c1fccf189c8225fece1cd7c3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3",
|
||||||
|
"reference": "7562c05391f42701c1fccf189c8225fece1cd7c3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
||||||
|
"react/event-loop": "^1.2",
|
||||||
|
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||||
|
"react/async": "^4.3 || ^3 || ^2",
|
||||||
|
"react/promise-timer": "^1.11"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Dns\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Async DNS resolver for ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"async",
|
||||||
|
"dns",
|
||||||
|
"dns-resolver",
|
||||||
|
"reactphp"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/dns/issues",
|
||||||
|
"source": "https://github.com/reactphp/dns/tree/v1.14.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-11-18T19:34:28+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/event-loop",
|
||||||
|
"version": "v1.6.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/event-loop.git",
|
||||||
|
"reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
|
||||||
|
"reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\EventLoop\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
|
||||||
|
"keywords": [
|
||||||
|
"asynchronous",
|
||||||
|
"event-loop"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/event-loop/issues",
|
||||||
|
"source": "https://github.com/reactphp/event-loop/tree/v1.6.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-11-17T20:46:25+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/promise",
|
||||||
|
"version": "v3.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/promise.git",
|
||||||
|
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||||
|
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "1.12.28 || 1.4.10",
|
||||||
|
"phpunit/phpunit": "^9.6 || ^7.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions_include.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Promise\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||||
|
"keywords": [
|
||||||
|
"promise",
|
||||||
|
"promises"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/promise/issues",
|
||||||
|
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-08-19T18:57:03+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/socket",
|
||||||
|
"version": "v1.17.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/socket.git",
|
||||||
|
"reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08",
|
||||||
|
"reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"react/dns": "^1.13",
|
||||||
|
"react/event-loop": "^1.2",
|
||||||
|
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
|
||||||
|
"react/stream": "^1.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||||
|
"react/async": "^4.3 || ^3.3 || ^2",
|
||||||
|
"react/promise-stream": "^1.4",
|
||||||
|
"react/promise-timer": "^1.11"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Socket\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"Connection",
|
||||||
|
"Socket",
|
||||||
|
"async",
|
||||||
|
"reactphp",
|
||||||
|
"stream"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/socket/issues",
|
||||||
|
"source": "https://github.com/reactphp/socket/tree/v1.17.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-11-19T20:47:34+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/stream",
|
||||||
|
"version": "v1.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/stream.git",
|
||||||
|
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
|
||||||
|
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||||
|
"php": ">=5.3.8",
|
||||||
|
"react/event-loop": "^1.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"clue/stream-filter": "~1.2",
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Stream\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"event-driven",
|
||||||
|
"io",
|
||||||
|
"non-blocking",
|
||||||
|
"pipe",
|
||||||
|
"reactphp",
|
||||||
|
"readable",
|
||||||
|
"stream",
|
||||||
|
"writable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/stream/issues",
|
||||||
|
"source": "https://github.com/reactphp/stream/tree/v1.4.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-06-11T12:45:25+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setasign/fpdf",
|
"name": "setasign/fpdf",
|
||||||
"version": "1.8.6",
|
"version": "1.8.6",
|
||||||
@@ -4079,6 +4697,96 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-07-26T11:53:26+00:00"
|
"time": "2023-07-26T11:53:26+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/routing",
|
||||||
|
"version": "v5.4.48",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/routing.git",
|
||||||
|
"reference": "dd08c19879a9b37ff14fd30dcbdf99a4cf045db1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/routing/zipball/dd08c19879a9b37ff14fd30dcbdf99a4cf045db1",
|
||||||
|
"reference": "dd08c19879a9b37ff14fd30dcbdf99a4cf045db1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/deprecation-contracts": "^2.1|^3",
|
||||||
|
"symfony/polyfill-php80": "^1.16"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/annotations": "<1.12",
|
||||||
|
"symfony/config": "<5.3",
|
||||||
|
"symfony/dependency-injection": "<4.4",
|
||||||
|
"symfony/yaml": "<4.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/annotations": "^1.12|^2",
|
||||||
|
"psr/log": "^1|^2|^3",
|
||||||
|
"symfony/config": "^5.3|^6.0",
|
||||||
|
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
|
||||||
|
"symfony/expression-language": "^4.4|^5.0|^6.0",
|
||||||
|
"symfony/http-foundation": "^4.4|^5.0|^6.0",
|
||||||
|
"symfony/yaml": "^4.4|^5.0|^6.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/config": "For using the all-in-one router or any loader",
|
||||||
|
"symfony/expression-language": "For using expression matching",
|
||||||
|
"symfony/http-foundation": "For using a Symfony Request object",
|
||||||
|
"symfony/yaml": "For using the YAML loader"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Routing\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Maps an HTTP request to a set of configuration variables",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"router",
|
||||||
|
"routing",
|
||||||
|
"uri",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/routing/tree/v5.4.48"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-11-12T18:20:21+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/service-contracts",
|
"name": "symfony/service-contracts",
|
||||||
"version": "v2.5.4",
|
"version": "v2.5.4",
|
||||||
|
|||||||
52
src/vendor/cboden/ratchet/.github/workflows/ci.yml
vendored
Normal file
52
src/vendor/cboden/ratchet/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: "CI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
schedule:
|
||||||
|
- cron: "42 3 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpunit:
|
||||||
|
name: "PHPUnit"
|
||||||
|
runs-on: "ubuntu-20.04"
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- "5.4"
|
||||||
|
- "5.5"
|
||||||
|
- "5.6"
|
||||||
|
- "7.0"
|
||||||
|
- "7.1"
|
||||||
|
- "7.2"
|
||||||
|
- "7.3"
|
||||||
|
- "7.4"
|
||||||
|
dependencies:
|
||||||
|
- "highest"
|
||||||
|
include:
|
||||||
|
- dependencies: "lowest"
|
||||||
|
php-version: "5.4"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2"
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: "Install PHP"
|
||||||
|
uses: "shivammathur/setup-php@v2"
|
||||||
|
with:
|
||||||
|
php-version: "${{ matrix.php-version }}"
|
||||||
|
coverage: "none"
|
||||||
|
ini-values: "zend.assertions=1"
|
||||||
|
|
||||||
|
- name: "Install dependencies with Composer"
|
||||||
|
uses: "ramsey/composer-install@v1"
|
||||||
|
with:
|
||||||
|
dependency-versions: "${{ matrix.dependencies }}"
|
||||||
|
|
||||||
|
- name: "Run PHPUnit"
|
||||||
|
run: "vendor/bin/phpunit"
|
||||||
150
src/vendor/cboden/ratchet/CHANGELOG.md
vendored
Normal file
150
src/vendor/cboden/ratchet/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
### Legend
|
||||||
|
|
||||||
|
* "BC": Backwards compatibility break (from public component APIs)
|
||||||
|
* "BF": Bug fix
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* 0.4.4 (2021-12-11)
|
||||||
|
* Correct and update dependencies for forward compatibility
|
||||||
|
* Added context for React Socket server to App
|
||||||
|
* Use non-deprecated Guzzle API calls
|
||||||
|
|
||||||
|
* 0.4.3 (2020-06-04)
|
||||||
|
* BF: Fixed interface acceptable regression in `App`
|
||||||
|
* Update RFC6455 library with latest fixes
|
||||||
|
|
||||||
|
* 0.4.2 (2020-01-27)
|
||||||
|
* Support Symfony 5
|
||||||
|
* BF: Use phpunit from vendor directory
|
||||||
|
* Allow disabling of xdebug warning by defining `RATCHET_DISABLE_XDEBUG_WARN`
|
||||||
|
* Stop using `LoopInterface::tick()` for testing
|
||||||
|
|
||||||
|
* 0.4.1 (2017-12-11)
|
||||||
|
* Only enableKeepAlive in App if no WsServer passed allowing user to set their own timeout duration
|
||||||
|
* Support Symfony 4
|
||||||
|
* BF: Plug NOOP controller in connection from router in case of misbehaving client
|
||||||
|
* BF: Raise error from invalid WAMP payload
|
||||||
|
|
||||||
|
* 0.4 (2017-09-14)
|
||||||
|
* BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object
|
||||||
|
* Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface
|
||||||
|
* Added heartbeat support via ping/pong in WsServer
|
||||||
|
* BC: No longer support old (and insecure) Hixie76 and Hybi protocols
|
||||||
|
* BC: No longer support disabling UTF-8 checks
|
||||||
|
* BC: The Session component implements HttpServerInterface instead of WsServerInterface
|
||||||
|
* BC: PHP 5.3 no longer supported
|
||||||
|
* BC: Update to newer version of react/socket dependency
|
||||||
|
* BC: WAMP topics reduced to 0 subscriptions are deleted, new subs to same name will result in new Topic instance
|
||||||
|
* Significant performance enhancements
|
||||||
|
|
||||||
|
* 0.3.6 (2017-01-06)
|
||||||
|
* BF: Keep host and scheme in HTTP request object attatched to connection
|
||||||
|
* BF: Return correct HTTP response (405) when non-GET request made
|
||||||
|
|
||||||
|
* 0.3.5 (2016-05-25)
|
||||||
|
* BF: Unmask responding close frame
|
||||||
|
* Added write handler for PHP session serializer
|
||||||
|
|
||||||
|
* 0.3.4 (2015-12-23)
|
||||||
|
* BF: Edge case where version check wasn't run on message coalesce
|
||||||
|
* BF: Session didn't start when using pdo_sqlite
|
||||||
|
* BF: WAMP currie prefix check when using '#'
|
||||||
|
* Compatibility with Symfony 3
|
||||||
|
|
||||||
|
* 0.3.3 (2015-05-26)
|
||||||
|
* BF: Framing bug on large messages upon TCP fragmentation
|
||||||
|
* BF: Symfony Router query parameter defaults applied to Request
|
||||||
|
* BF: WAMP CURIE on all URIs
|
||||||
|
* OriginCheck rules applied to FlashPolicy
|
||||||
|
* Switched from PSR-0 to PSR-4
|
||||||
|
|
||||||
|
* 0.3.2 (2014-06-08)
|
||||||
|
* BF: No messages after closing handshake (fixed rare race condition causing 100% CPU)
|
||||||
|
* BF: Fixed accidental BC break from v0.3.1
|
||||||
|
* Added autoDelete parameter to Topic to destroy when empty of connections
|
||||||
|
* Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App)
|
||||||
|
* Normalized Exceptions in WAMP
|
||||||
|
|
||||||
|
* 0.3.1 (2014-05-26)
|
||||||
|
* Added query parameter support to Router, set in HTTP request (ws://server?hello=world)
|
||||||
|
* HHVM compatibility
|
||||||
|
* BF: React/0.4 support; CPU starvation bug fixes
|
||||||
|
* BF: Allow App::route to ignore Host header
|
||||||
|
* Added expected filters to WAMP Topic broadcast method
|
||||||
|
* Resource cleanup in WAMP TopicManager
|
||||||
|
|
||||||
|
* 0.3.0 (2013-10-14)
|
||||||
|
* Added the `App` class to help making Ratchet so easy to use it's silly
|
||||||
|
* BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks
|
||||||
|
* Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router
|
||||||
|
* BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer
|
||||||
|
* BF: Single sub-protocol selection to conform with RFC6455
|
||||||
|
* BF: Sanity checks on WAMP protocol to prevent errors
|
||||||
|
|
||||||
|
* 0.2.8 (2013-09-19)
|
||||||
|
* React 0.3 support
|
||||||
|
|
||||||
|
* 0.2.7 (2013-06-09)
|
||||||
|
* BF: Sub-protocol negotation with Guzzle 3.6
|
||||||
|
|
||||||
|
* 0.2.6 (2013-06-01)
|
||||||
|
* Guzzle 3.6 support
|
||||||
|
|
||||||
|
* 0.2.5 (2013-04-01)
|
||||||
|
* Fixed Hixie-76 handshake bug
|
||||||
|
|
||||||
|
* 0.2.4 (2013-03-09)
|
||||||
|
* Support for Symfony 2.2 and Guzzle 2.3
|
||||||
|
* Minor bug fixes when handling errors
|
||||||
|
|
||||||
|
* 0.2.3 (2012-11-21)
|
||||||
|
* Bumped dep: Guzzle to v3, React to v0.2.4
|
||||||
|
* More tests
|
||||||
|
|
||||||
|
* 0.2.2 (2012-10-20)
|
||||||
|
* Bumped deps to use React v0.2
|
||||||
|
|
||||||
|
* 0.2.1 (2012-10-13)
|
||||||
|
* BF: No more UTF-8 warnings in browsers (no longer sending empty sub-protocol string)
|
||||||
|
* Documentation corrections
|
||||||
|
* Using new composer structure
|
||||||
|
|
||||||
|
* 0.2 (2012-09-07)
|
||||||
|
* Ratchet passes every non-binary-frame test from the Autobahn Testsuite
|
||||||
|
* Major performance improvements
|
||||||
|
* BC: Renamed "WampServer" to "ServerProtocol"
|
||||||
|
* BC: New "WampServer" component passes Topic container objects of subscribed Connections
|
||||||
|
* Option to turn off UTF-8 checks in order to increase performance
|
||||||
|
* Switched dependency guzzle/guzzle to guzzle/http (no API changes)
|
||||||
|
* mbstring no longer required
|
||||||
|
|
||||||
|
* 0.1.5 (2012-07-12)
|
||||||
|
* BF: Error where service wouldn't run on PHP <= 5.3.8
|
||||||
|
* Dependency library updates
|
||||||
|
|
||||||
|
* 0.1.4 (2012-06-17)
|
||||||
|
* Fixed dozens of failing AB tests
|
||||||
|
* BF: Proper socket buffer handling
|
||||||
|
|
||||||
|
* 0.1.3 (2012-06-15)
|
||||||
|
* Major refactor inside WebSocket protocol handling, more loosley coupled
|
||||||
|
* BF: Proper error handling on failed WebSocket connections
|
||||||
|
* BF: Handle TCP message concatenation
|
||||||
|
* Inclusion of the AutobahnTestSuite checking WebSocket protocol compliance
|
||||||
|
* mb_string now a requirement
|
||||||
|
|
||||||
|
* 0.1.2 (2012-05-19)
|
||||||
|
* BC/BF: Updated WAMP API to coincide with the official spec
|
||||||
|
* Tweaks to improve running as a long lived process
|
||||||
|
|
||||||
|
* 0.1.1 (2012-05-14)
|
||||||
|
* Separated interfaces allowing WebSockets to support multiple sub protocols
|
||||||
|
* BF: remoteAddress variable on connections returns proper value
|
||||||
|
|
||||||
|
* 0.1 (2012-05-11)
|
||||||
|
* First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList
|
||||||
|
* I/O now handled by React, making Ratchet fully asynchronous
|
||||||
19
src/vendor/cboden/ratchet/LICENSE
vendored
Normal file
19
src/vendor/cboden/ratchet/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2011 Chris Boden
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
42
src/vendor/cboden/ratchet/Makefile
vendored
Normal file
42
src/vendor/cboden/ratchet/Makefile
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# This file is intended to ease the author's development and testing process
|
||||||
|
# Users do not need to use `make`; Ratchet does not need to be compiled
|
||||||
|
|
||||||
|
test:
|
||||||
|
vendor/bin/phpunit
|
||||||
|
|
||||||
|
cover:
|
||||||
|
vendor/bin/phpunit --coverage-text --coverage-html=reports/coverage
|
||||||
|
|
||||||
|
abtests:
|
||||||
|
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8001 LibEvent &
|
||||||
|
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8002 StreamSelect &
|
||||||
|
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv &
|
||||||
|
wstest -m testeeserver -w ws://localhost:8000 &
|
||||||
|
sleep 1
|
||||||
|
wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json
|
||||||
|
killall php wstest
|
||||||
|
|
||||||
|
abtest:
|
||||||
|
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect &
|
||||||
|
sleep 1
|
||||||
|
wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json
|
||||||
|
killall php
|
||||||
|
|
||||||
|
profile:
|
||||||
|
php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent &
|
||||||
|
sleep 1
|
||||||
|
wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json
|
||||||
|
killall php
|
||||||
|
|
||||||
|
apidocs:
|
||||||
|
apigen --title Ratchet -d reports/api \
|
||||||
|
-s src/ \
|
||||||
|
-s vendor/ratchet/rfc6455/src \
|
||||||
|
-s vendor/react/event-loop/src \
|
||||||
|
-s vendor/react/socket/src \
|
||||||
|
-s vendor/react/stream/src \
|
||||||
|
-s vendor/psr/http-message/src \
|
||||||
|
-s vendor/symfony/http-foundation/Session \
|
||||||
|
-s vendor/symfony/routing \
|
||||||
|
-s vendor/evenement/evenement/src/Evenement \
|
||||||
|
--exclude=vendor/symfony/routing/Tests \
|
||||||
87
src/vendor/cboden/ratchet/README.md
vendored
Normal file
87
src/vendor/cboden/ratchet/README.md
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Ratchet
|
||||||
|
|
||||||
|
[![GitHub Actions][GA Image]][GA Link]
|
||||||
|
[](http://socketo.me/reports/ab/index.html)
|
||||||
|
[](https://packagist.org/packages/cboden/ratchet)
|
||||||
|
|
||||||
|
A PHP library for asynchronously serving WebSockets.
|
||||||
|
Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Shell access is required and root access is recommended.
|
||||||
|
To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access.
|
||||||
|
In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines.
|
||||||
|
You can find more details in the [server conf docs](http://socketo.me/docs/deploy#server_configuration).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
User and API documentation is available on Ratchet's website: http://socketo.me
|
||||||
|
|
||||||
|
See https://github.com/cboden/Ratchet-examples for some out-of-the-box working demos using Ratchet.
|
||||||
|
|
||||||
|
Need help? Have a question? Want to provide feedback? Write a message on the [Google Groups Mailing List](https://groups.google.com/forum/#!forum/ratchet-php).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### A quick example
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
// Make sure composer dependencies have been installed
|
||||||
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* chat.php
|
||||||
|
* Send any incoming messages to all connected clients (except sender)
|
||||||
|
*/
|
||||||
|
class MyChat implements MessageComponentInterface {
|
||||||
|
protected $clients;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->clients = new \SplObjectStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$this->clients->attach($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
foreach ($this->clients as $client) {
|
||||||
|
if ($from != $client) {
|
||||||
|
$client->send($msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
$this->clients->detach($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the server application through the WebSocket protocol on port 8080
|
||||||
|
$app = new Ratchet\App('localhost', 8080);
|
||||||
|
$app->route('/chat', new MyChat, array('*'));
|
||||||
|
$app->route('/echo', new Ratchet\Server\EchoServer, array('*'));
|
||||||
|
$app->run();
|
||||||
|
```
|
||||||
|
|
||||||
|
$ php chat.php
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Then some JavaScript in the browser:
|
||||||
|
var conn = new WebSocket('ws://localhost:8080/echo');
|
||||||
|
conn.onmessage = function(e) { console.log(e.data); };
|
||||||
|
conn.onopen = function(e) { conn.send('Hello Me!'); };
|
||||||
|
```
|
||||||
|
|
||||||
|
[GA Image]: https://github.com/ratchetphp/Ratchet/workflows/CI/badge.svg
|
||||||
|
|
||||||
|
[GA Link]: https://github.com/ratchetphp/Ratchet/actions?query=workflow%3A%22CI%22+branch%3Amaster
|
||||||
8
src/vendor/cboden/ratchet/SECURITY.md
vendored
Normal file
8
src/vendor/cboden/ratchet/SECURITY.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please report security issues to:
|
||||||
|
|
||||||
|
* Chris Boden [cboden@gmail.com](cboden@gmail.com)
|
||||||
|
* Matt Bonneau [matt@bonneau.net](matt@bonneau.net)
|
||||||
40
src/vendor/cboden/ratchet/composer.json
vendored
Normal file
40
src/vendor/cboden/ratchet/composer.json
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "cboden/ratchet"
|
||||||
|
, "type": "library"
|
||||||
|
, "description": "PHP WebSocket library"
|
||||||
|
, "keywords": ["WebSockets", "Server", "Ratchet", "Sockets", "WebSocket"]
|
||||||
|
, "homepage": "http://socketo.me"
|
||||||
|
, "license": "MIT"
|
||||||
|
, "authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Boden"
|
||||||
|
, "email": "cboden@gmail.com"
|
||||||
|
, "role": "Developer"
|
||||||
|
}
|
||||||
|
, {
|
||||||
|
"name": "Matt Bonneau"
|
||||||
|
, "role": "Developer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, "support": {
|
||||||
|
"issues": "https://github.com/ratchetphp/Ratchet/issues"
|
||||||
|
, "chat": "https://gitter.im/reactphp/reactphp"
|
||||||
|
}
|
||||||
|
, "autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ratchet\\": "src/Ratchet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, "require": {
|
||||||
|
"php": ">=5.4.2"
|
||||||
|
, "ratchet/rfc6455": "^0.3.1"
|
||||||
|
, "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5"
|
||||||
|
, "react/event-loop": ">=0.4"
|
||||||
|
, "guzzlehttp/psr7": "^1.7|^2.0"
|
||||||
|
, "symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0"
|
||||||
|
, "symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.0"
|
||||||
|
}
|
||||||
|
, "require-dev": {
|
||||||
|
"phpunit/phpunit": "~4.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/vendor/cboden/ratchet/phpunit.xml.dist
vendored
Normal file
30
src/vendor/cboden/ratchet/phpunit.xml.dist
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
forceCoversAnnotation="true"
|
||||||
|
mapTestClassNameToCoveredClassName="true"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
colors="true"
|
||||||
|
backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
stopOnError="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="unit">
|
||||||
|
<directory>./tests/unit/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="integration">
|
||||||
|
<directory>./tests/integration/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./src/</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
||||||
41
src/vendor/cboden/ratchet/src/Ratchet/AbstractConnectionDecorator.php
vendored
Normal file
41
src/vendor/cboden/ratchet/src/Ratchet/AbstractConnectionDecorator.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps ConnectionInterface objects via the decorator pattern but allows
|
||||||
|
* parameters to bubble through with magic methods
|
||||||
|
* @todo It sure would be nice if I could make most of this a trait...
|
||||||
|
*/
|
||||||
|
abstract class AbstractConnectionDecorator implements ConnectionInterface {
|
||||||
|
/**
|
||||||
|
* @var ConnectionInterface
|
||||||
|
*/
|
||||||
|
protected $wrappedConn;
|
||||||
|
|
||||||
|
public function __construct(ConnectionInterface $conn) {
|
||||||
|
$this->wrappedConn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ConnectionInterface
|
||||||
|
*/
|
||||||
|
protected function getConnection() {
|
||||||
|
return $this->wrappedConn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __set($name, $value) {
|
||||||
|
$this->wrappedConn->$name = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($name) {
|
||||||
|
return $this->wrappedConn->$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __isset($name) {
|
||||||
|
return isset($this->wrappedConn->$name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __unset($name) {
|
||||||
|
unset($this->wrappedConn->$name);
|
||||||
|
}
|
||||||
|
}
|
||||||
147
src/vendor/cboden/ratchet/src/Ratchet/App.php
vendored
Normal file
147
src/vendor/cboden/ratchet/src/Ratchet/App.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
use React\EventLoop\LoopInterface;
|
||||||
|
use React\EventLoop\Factory as LoopFactory;
|
||||||
|
use React\Socket\Server as Reactor;
|
||||||
|
use React\Socket\SecureServer as SecureReactor;
|
||||||
|
use Ratchet\Http\HttpServerInterface;
|
||||||
|
use Ratchet\Http\OriginCheck;
|
||||||
|
use Ratchet\Wamp\WampServerInterface;
|
||||||
|
use Ratchet\Server\IoServer;
|
||||||
|
use Ratchet\Server\FlashPolicy;
|
||||||
|
use Ratchet\Http\HttpServer;
|
||||||
|
use Ratchet\Http\Router;
|
||||||
|
use Ratchet\WebSocket\MessageComponentInterface as WsMessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServer;
|
||||||
|
use Ratchet\Wamp\WampServer;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\RequestContext;
|
||||||
|
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An opinionated facade class to quickly and easily create a WebSocket server.
|
||||||
|
* A few configuration assumptions are made and some best-practice security conventions are applied by default.
|
||||||
|
*/
|
||||||
|
class App {
|
||||||
|
/**
|
||||||
|
* @var \Symfony\Component\Routing\RouteCollection
|
||||||
|
*/
|
||||||
|
public $routes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\Server\IoServer
|
||||||
|
*/
|
||||||
|
public $flashServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\Server\IoServer
|
||||||
|
*/
|
||||||
|
protected $_server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Host passed in construct used for same origin policy
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $httpHost;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* The port the socket is listening
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $_routeCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');`
|
||||||
|
* @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843
|
||||||
|
* @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine.
|
||||||
|
* @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you.
|
||||||
|
* @param array $context
|
||||||
|
*/
|
||||||
|
public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null, $context = array()) {
|
||||||
|
if (extension_loaded('xdebug') && getenv('RATCHET_DISABLE_XDEBUG_WARN') === false) {
|
||||||
|
trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $loop) {
|
||||||
|
$loop = LoopFactory::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->httpHost = $httpHost;
|
||||||
|
$this->port = $port;
|
||||||
|
|
||||||
|
$socket = new Reactor($address . ':' . $port, $loop, $context);
|
||||||
|
|
||||||
|
$this->routes = new RouteCollection;
|
||||||
|
$this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop);
|
||||||
|
|
||||||
|
$policy = new FlashPolicy;
|
||||||
|
$policy->addAllowedAccess($httpHost, 80);
|
||||||
|
$policy->addAllowedAccess($httpHost, $port);
|
||||||
|
|
||||||
|
if (80 == $port) {
|
||||||
|
$flashUri = '0.0.0.0:843';
|
||||||
|
} else {
|
||||||
|
$flashUri = 8843;
|
||||||
|
}
|
||||||
|
$flashSock = new Reactor($flashUri, $loop);
|
||||||
|
$this->flashServer = new IoServer($policy, $flashSock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an endpoint/application to the server
|
||||||
|
* @param string $path The URI the client will connect to
|
||||||
|
* @param ComponentInterface $controller Your application to server for the route. If not specified, assumed to be for a WebSocket
|
||||||
|
* @param array $allowedOrigins An array of hosts allowed to connect (same host by default), ['*'] for any
|
||||||
|
* @param string $httpHost Override the $httpHost variable provided in the __construct
|
||||||
|
* @return ComponentInterface|WsServer
|
||||||
|
*/
|
||||||
|
public function route($path, ComponentInterface $controller, array $allowedOrigins = array(), $httpHost = null) {
|
||||||
|
if ($controller instanceof HttpServerInterface || $controller instanceof WsServer) {
|
||||||
|
$decorated = $controller;
|
||||||
|
} elseif ($controller instanceof WampServerInterface) {
|
||||||
|
$decorated = new WsServer(new WampServer($controller));
|
||||||
|
$decorated->enableKeepAlive($this->_server->loop);
|
||||||
|
} elseif ($controller instanceof MessageComponentInterface || $controller instanceof WsMessageComponentInterface) {
|
||||||
|
$decorated = new WsServer($controller);
|
||||||
|
$decorated->enableKeepAlive($this->_server->loop);
|
||||||
|
} else {
|
||||||
|
$decorated = $controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpHost === null) {
|
||||||
|
$httpHost = $this->httpHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedOrigins = array_values($allowedOrigins);
|
||||||
|
if (0 === count($allowedOrigins)) {
|
||||||
|
$allowedOrigins[] = $httpHost;
|
||||||
|
}
|
||||||
|
if ('*' !== $allowedOrigins[0]) {
|
||||||
|
$decorated = new OriginCheck($decorated, $allowedOrigins);
|
||||||
|
}
|
||||||
|
|
||||||
|
//allow origins in flash policy server
|
||||||
|
if(empty($this->flashServer) === false) {
|
||||||
|
foreach($allowedOrigins as $allowedOrgin) {
|
||||||
|
$this->flashServer->app->addAllowedAccess($allowedOrgin, $this->port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array('Origin' => $this->httpHost), array(), $httpHost, array(), array('GET')));
|
||||||
|
|
||||||
|
return $decorated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the server by entering the event loop
|
||||||
|
*/
|
||||||
|
public function run() {
|
||||||
|
$this->_server->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/vendor/cboden/ratchet/src/Ratchet/ComponentInterface.php
vendored
Normal file
31
src/vendor/cboden/ratchet/src/Ratchet/ComponentInterface.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the interface to build a Ratchet application with.
|
||||||
|
* It implements the decorator pattern to build an application stack
|
||||||
|
*/
|
||||||
|
interface ComponentInterface {
|
||||||
|
/**
|
||||||
|
* When a new connection is opened it will be passed to this method
|
||||||
|
* @param ConnectionInterface $conn The socket/connection that just connected to your application
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
function onOpen(ConnectionInterface $conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called before or after a socket is closed (depends on how it's closed). SendMessage to $conn will not result in an error if it has already been closed.
|
||||||
|
* @param ConnectionInterface $conn The socket/connection that is closing/closed
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
function onClose(ConnectionInterface $conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is an error with one of the sockets, or somewhere in the application where an Exception is thrown,
|
||||||
|
* the Exception is sent back down the stack, handled by the Server and bubbled back up the application through this method
|
||||||
|
* @param ConnectionInterface $conn
|
||||||
|
* @param \Exception $e
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
function onError(ConnectionInterface $conn, \Exception $e);
|
||||||
|
}
|
||||||
26
src/vendor/cboden/ratchet/src/Ratchet/ConnectionInterface.php
vendored
Normal file
26
src/vendor/cboden/ratchet/src/Ratchet/ConnectionInterface.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version of Ratchet being used
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const VERSION = 'Ratchet/0.4.4';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A proxy object representing a connection to the application
|
||||||
|
* This acts as a container to store data (in memory) about the connection
|
||||||
|
*/
|
||||||
|
interface ConnectionInterface {
|
||||||
|
/**
|
||||||
|
* Send data to the connection
|
||||||
|
* @param string $data
|
||||||
|
* @return \Ratchet\ConnectionInterface
|
||||||
|
*/
|
||||||
|
function send($data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the connection
|
||||||
|
*/
|
||||||
|
function close();
|
||||||
|
}
|
||||||
22
src/vendor/cboden/ratchet/src/Ratchet/Http/CloseResponseTrait.php
vendored
Normal file
22
src/vendor/cboden/ratchet/src/Ratchet/Http/CloseResponseTrait.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use GuzzleHttp\Psr7\Message;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
|
||||||
|
trait CloseResponseTrait {
|
||||||
|
/**
|
||||||
|
* Close a connection with an HTTP response
|
||||||
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
|
* @param int $code HTTP status code
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
private function close(ConnectionInterface $conn, $code = 400, array $additional_headers = []) {
|
||||||
|
$response = new Response($code, array_merge([
|
||||||
|
'X-Powered-By' => \Ratchet\VERSION
|
||||||
|
], $additional_headers));
|
||||||
|
|
||||||
|
$conn->send(Message::toString($response));
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php
vendored
Normal file
64
src/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\MessageInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use GuzzleHttp\Psr7\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class receives streaming data from a client request
|
||||||
|
* and parses HTTP headers, returning a PSR-7 Request object
|
||||||
|
* once it's been buffered
|
||||||
|
*/
|
||||||
|
class HttpRequestParser implements MessageInterface {
|
||||||
|
const EOM = "\r\n\r\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes the request can be
|
||||||
|
* This is a security measure to prevent attacks
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxSize = 4096;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\ConnectionInterface $context
|
||||||
|
* @param string $data Data stream to buffer
|
||||||
|
* @return \Psr\Http\Message\RequestInterface
|
||||||
|
* @throws \OverflowException If the message buffer has become too large
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $context, $data) {
|
||||||
|
if (!isset($context->httpBuffer)) {
|
||||||
|
$context->httpBuffer = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->httpBuffer .= $data;
|
||||||
|
|
||||||
|
if (strlen($context->httpBuffer) > (int)$this->maxSize) {
|
||||||
|
throw new \OverflowException("Maximum buffer size of {$this->maxSize} exceeded parsing HTTP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isEom($context->httpBuffer)) {
|
||||||
|
$request = $this->parse($context->httpBuffer);
|
||||||
|
|
||||||
|
unset($context->httpBuffer);
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the message has been buffered as per the HTTP specification
|
||||||
|
* @param string $message
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isEom($message) {
|
||||||
|
return (boolean)strpos($message, static::EOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $headers
|
||||||
|
* @return \Psr\Http\Message\RequestInterface
|
||||||
|
*/
|
||||||
|
public function parse($headers) {
|
||||||
|
return Message::parseRequest($headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php
vendored
Normal file
76
src/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class HttpServer implements MessageComponentInterface {
|
||||||
|
use CloseResponseTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffers incoming HTTP requests returning a Guzzle Request when coalesced
|
||||||
|
* @var HttpRequestParser
|
||||||
|
* @note May not expose this in the future, may do through facade methods
|
||||||
|
*/
|
||||||
|
protected $_reqParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\Http\HttpServerInterface
|
||||||
|
*/
|
||||||
|
protected $_httpServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param HttpServerInterface
|
||||||
|
*/
|
||||||
|
public function __construct(HttpServerInterface $component) {
|
||||||
|
$this->_httpServer = $component;
|
||||||
|
$this->_reqParser = new HttpRequestParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$conn->httpHeadersReceived = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
if (true !== $from->httpHeadersReceived) {
|
||||||
|
try {
|
||||||
|
if (null === ($request = $this->_reqParser->onMessage($from, $msg))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (\OverflowException $oe) {
|
||||||
|
return $this->close($from, 413);
|
||||||
|
}
|
||||||
|
|
||||||
|
$from->httpHeadersReceived = true;
|
||||||
|
|
||||||
|
return $this->_httpServer->onOpen($from, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_httpServer->onMessage($from, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
if ($conn->httpHeadersReceived) {
|
||||||
|
$this->_httpServer->onClose($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
if ($conn->httpHeadersReceived) {
|
||||||
|
$this->_httpServer->onError($conn, $e);
|
||||||
|
} else {
|
||||||
|
$this->close($conn, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/vendor/cboden/ratchet/src/Ratchet/Http/HttpServerInterface.php
vendored
Normal file
14
src/vendor/cboden/ratchet/src/Ratchet/Http/HttpServerInterface.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
interface HttpServerInterface extends MessageComponentInterface {
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
|
* @param \Psr\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!!
|
||||||
|
* @throws \UnexpectedValueException if a RequestInterface is not passed
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null);
|
||||||
|
}
|
||||||
18
src/vendor/cboden/ratchet/src/Ratchet/Http/NoOpHttpServerController.php
vendored
Normal file
18
src/vendor/cboden/ratchet/src/Ratchet/Http/NoOpHttpServerController.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
class NoOpHttpServerController implements HttpServerInterface {
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/vendor/cboden/ratchet/src/Ratchet/Http/OriginCheck.php
vendored
Normal file
65
src/vendor/cboden/ratchet/src/Ratchet/Http/OriginCheck.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A middleware to ensure JavaScript clients connecting are from the expected domain.
|
||||||
|
* This protects other websites from open WebSocket connections to your application.
|
||||||
|
* Note: This can be spoofed from non-web browser clients
|
||||||
|
*/
|
||||||
|
class OriginCheck implements HttpServerInterface {
|
||||||
|
use CloseResponseTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\MessageComponentInterface
|
||||||
|
*/
|
||||||
|
protected $_component;
|
||||||
|
|
||||||
|
public $allowedOrigins = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MessageComponentInterface $component Component/Application to decorate
|
||||||
|
* @param array $allowed An array of allowed domains that are allowed to connect from
|
||||||
|
*/
|
||||||
|
public function __construct(MessageComponentInterface $component, array $allowed = []) {
|
||||||
|
$this->_component = $component;
|
||||||
|
$this->allowedOrigins += $allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
|
$header = (string)$request->getHeader('Origin')[0];
|
||||||
|
$origin = parse_url($header, PHP_URL_HOST) ?: $header;
|
||||||
|
|
||||||
|
if (!in_array($origin, $this->allowedOrigins)) {
|
||||||
|
return $this->close($conn, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_component->onOpen($conn, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
return $this->_component->onMessage($from, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onClose(ConnectionInterface $conn) {
|
||||||
|
return $this->_component->onClose($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
return $this->_component->onError($conn, $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/vendor/cboden/ratchet/src/Ratchet/Http/Router.php
vendored
Normal file
96
src/vendor/cboden/ratchet/src/Ratchet/Http/Router.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
|
||||||
|
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
||||||
|
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||||
|
use GuzzleHttp\Psr7\Query;
|
||||||
|
|
||||||
|
class Router implements HttpServerInterface {
|
||||||
|
use CloseResponseTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
|
||||||
|
*/
|
||||||
|
protected $_matcher;
|
||||||
|
|
||||||
|
private $_noopController;
|
||||||
|
|
||||||
|
public function __construct(UrlMatcherInterface $matcher) {
|
||||||
|
$this->_matcher = $matcher;
|
||||||
|
$this->_noopController = new NoOpHttpServerController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
|
if (null === $request) {
|
||||||
|
throw new \UnexpectedValueException('$request can not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->controller = $this->_noopController;
|
||||||
|
|
||||||
|
$uri = $request->getUri();
|
||||||
|
|
||||||
|
$context = $this->_matcher->getContext();
|
||||||
|
$context->setMethod($request->getMethod());
|
||||||
|
$context->setHost($uri->getHost());
|
||||||
|
|
||||||
|
try {
|
||||||
|
$route = $this->_matcher->match($uri->getPath());
|
||||||
|
} catch (MethodNotAllowedException $nae) {
|
||||||
|
return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods()));
|
||||||
|
} catch (ResourceNotFoundException $nfe) {
|
||||||
|
return $this->close($conn, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($route['_controller']) && class_exists($route['_controller'])) {
|
||||||
|
$route['_controller'] = new $route['_controller'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($route['_controller'] instanceof HttpServerInterface)) {
|
||||||
|
throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = [];
|
||||||
|
foreach($route as $key => $value) {
|
||||||
|
if ((is_string($key)) && ('_' !== substr($key, 0, 1))) {
|
||||||
|
$parameters[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$parameters = array_merge($parameters, Query::parse($uri->getQuery() ?: ''));
|
||||||
|
|
||||||
|
$request = $request->withUri($uri->withQuery(Query::build($parameters)));
|
||||||
|
|
||||||
|
$conn->controller = $route['_controller'];
|
||||||
|
$conn->controller->onOpen($conn, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
$from->controller->onMessage($from, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
if (isset($conn->controller)) {
|
||||||
|
$conn->controller->onClose($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
if (isset($conn->controller)) {
|
||||||
|
$conn->controller->onError($conn, $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/vendor/cboden/ratchet/src/Ratchet/MessageComponentInterface.php
vendored
Normal file
5
src/vendor/cboden/ratchet/src/Ratchet/MessageComponentInterface.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
|
||||||
|
interface MessageComponentInterface extends ComponentInterface, MessageInterface {
|
||||||
|
}
|
||||||
12
src/vendor/cboden/ratchet/src/Ratchet/MessageInterface.php
vendored
Normal file
12
src/vendor/cboden/ratchet/src/Ratchet/MessageInterface.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
|
||||||
|
interface MessageInterface {
|
||||||
|
/**
|
||||||
|
* Triggered when a client sends data through the socket
|
||||||
|
* @param \Ratchet\ConnectionInterface $from The socket/connection that sent the message to your application
|
||||||
|
* @param string $msg The message received
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
function onMessage(ConnectionInterface $from, $msg);
|
||||||
|
}
|
||||||
23
src/vendor/cboden/ratchet/src/Ratchet/Server/EchoServer.php
vendored
Normal file
23
src/vendor/cboden/ratchet/src/Ratchet/Server/EchoServer.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple Ratchet application that will reply to all messages with the message it received
|
||||||
|
*/
|
||||||
|
class EchoServer implements MessageComponentInterface {
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
$from->send($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
200
src/vendor/cboden/ratchet/src/Ratchet/Server/FlashPolicy.php
vendored
Normal file
200
src/vendor/cboden/ratchet/src/Ratchet/Server/FlashPolicy.php
vendored
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An app to go on a server stack to pass a policy file to a Flash socket
|
||||||
|
* Useful if you're using Flash as a WebSocket polyfill on IE
|
||||||
|
* Be sure to run your server instance on port 843
|
||||||
|
* By default this lets accepts everything, make sure you tighten the rules up for production
|
||||||
|
* @final
|
||||||
|
* @link http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html
|
||||||
|
* @link http://learn.adobe.com/wiki/download/attachments/64389123/CrossDomain_PolicyFile_Specification.pdf?version=1
|
||||||
|
* @link view-source:http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd
|
||||||
|
*/
|
||||||
|
class FlashPolicy implements MessageComponentInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the root policy node
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_policy = '<?xml version="1.0"?><!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"><cross-domain-policy></cross-domain-policy>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores an array of allowed domains and their ports
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $_access = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_siteControl = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_cache = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_cacheValid = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a domain to an allowed access list.
|
||||||
|
*
|
||||||
|
* @param string $domain Specifies a requesting domain to be granted access. Both named domains and IP
|
||||||
|
* addresses are acceptable values. Subdomains are considered different domains. A wildcard (*) can
|
||||||
|
* be used to match all domains when used alone, or multiple domains (subdomains) when used as a
|
||||||
|
* prefix for an explicit, second-level domain name separated with a dot (.)
|
||||||
|
* @param string $ports A comma-separated list of ports or range of ports that a socket connection
|
||||||
|
* is allowed to connect to. A range of ports is specified through a dash (-) between two port numbers.
|
||||||
|
* Ranges can be used with individual ports when separated with a comma. A single wildcard (*) can
|
||||||
|
* be used to allow all ports.
|
||||||
|
* @param bool $secure
|
||||||
|
* @throws \UnexpectedValueException
|
||||||
|
* @return FlashPolicy
|
||||||
|
*/
|
||||||
|
public function addAllowedAccess($domain, $ports = '*', $secure = false) {
|
||||||
|
if (!$this->validateDomain($domain)) {
|
||||||
|
throw new \UnexpectedValueException('Invalid domain');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->validatePorts($ports)) {
|
||||||
|
throw new \UnexpectedValueException('Invalid Port');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_access[] = array($domain, $ports, (boolean)$secure);
|
||||||
|
$this->_cacheValid = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all domains from the allowed access list.
|
||||||
|
*
|
||||||
|
* @return \Ratchet\Server\FlashPolicy
|
||||||
|
*/
|
||||||
|
public function clearAllowedAccess() {
|
||||||
|
$this->_access = array();
|
||||||
|
$this->_cacheValid = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* site-control defines the meta-policy for the current domain. A meta-policy specifies acceptable
|
||||||
|
* domain policy files other than the master policy file located in the target domain's root and named
|
||||||
|
* crossdomain.xml.
|
||||||
|
*
|
||||||
|
* @param string $permittedCrossDomainPolicies
|
||||||
|
* @throws \UnexpectedValueException
|
||||||
|
* @return FlashPolicy
|
||||||
|
*/
|
||||||
|
public function setSiteControl($permittedCrossDomainPolicies = 'all') {
|
||||||
|
if (!$this->validateSiteControl($permittedCrossDomainPolicies)) {
|
||||||
|
throw new \UnexpectedValueException('Invalid site control set');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_siteControl = $permittedCrossDomainPolicies;
|
||||||
|
$this->_cacheValid = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
if (!$this->_cacheValid) {
|
||||||
|
$this->_cache = $this->renderPolicy()->asXML();
|
||||||
|
$this->_cacheValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$from->send($this->_cache . "\0");
|
||||||
|
$from->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the crossdomain file based on the template policy
|
||||||
|
*
|
||||||
|
* @throws \UnexpectedValueException
|
||||||
|
* @return \SimpleXMLElement
|
||||||
|
*/
|
||||||
|
public function renderPolicy() {
|
||||||
|
$policy = new \SimpleXMLElement($this->_policy);
|
||||||
|
|
||||||
|
$siteControl = $policy->addChild('site-control');
|
||||||
|
|
||||||
|
if ($this->_siteControl == '') {
|
||||||
|
$this->setSiteControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
$siteControl->addAttribute('permitted-cross-domain-policies', $this->_siteControl);
|
||||||
|
|
||||||
|
if (empty($this->_access)) {
|
||||||
|
throw new \UnexpectedValueException('You must add a domain through addAllowedAccess()');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->_access as $access) {
|
||||||
|
$tmp = $policy->addChild('allow-access-from');
|
||||||
|
$tmp->addAttribute('domain', $access[0]);
|
||||||
|
$tmp->addAttribute('to-ports', $access[1]);
|
||||||
|
$tmp->addAttribute('secure', ($access[2] === true) ? 'true' : 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure the proper site control was passed
|
||||||
|
*
|
||||||
|
* @param string $permittedCrossDomainPolicies
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateSiteControl($permittedCrossDomainPolicies) {
|
||||||
|
//'by-content-type' and 'by-ftp-filename' are not available for sockets
|
||||||
|
return (bool)in_array($permittedCrossDomainPolicies, array('none', 'master-only', 'all'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate for proper domains (wildcards allowed)
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateDomain($domain) {
|
||||||
|
return (bool)preg_match("/^((http(s)?:\/\/)?([a-z0-9-_]+\.|\*\.)*([a-z0-9-_\.]+)|\*)$/i", $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure valid ports were passed
|
||||||
|
*
|
||||||
|
* @param string $port
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validatePorts($port) {
|
||||||
|
return (bool)preg_match('/^(\*|(\d+[,-]?)*\d+)$/', $port);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/vendor/cboden/ratchet/src/Ratchet/Server/IoConnection.php
vendored
Normal file
38
src/vendor/cboden/ratchet/src/Ratchet/Server/IoConnection.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use React\Socket\ConnectionInterface as ReactConn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
class IoConnection implements ConnectionInterface {
|
||||||
|
/**
|
||||||
|
* @var \React\Socket\ConnectionInterface
|
||||||
|
*/
|
||||||
|
protected $conn;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \React\Socket\ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function __construct(ReactConn $conn) {
|
||||||
|
$this->conn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($data) {
|
||||||
|
$this->conn->write($data);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function close() {
|
||||||
|
$this->conn->end();
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/vendor/cboden/ratchet/src/Ratchet/Server/IoServer.php
vendored
Normal file
140
src/vendor/cboden/ratchet/src/Ratchet/Server/IoServer.php
vendored
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use React\EventLoop\LoopInterface;
|
||||||
|
use React\Socket\ServerInterface;
|
||||||
|
use React\EventLoop\Factory as LoopFactory;
|
||||||
|
use React\Socket\Server as Reactor;
|
||||||
|
use React\Socket\SecureServer as SecureReactor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an open-ended socket to listen on a port for incoming connections.
|
||||||
|
* Events are delegated through this to attached applications
|
||||||
|
*/
|
||||||
|
class IoServer {
|
||||||
|
/**
|
||||||
|
* @var \React\EventLoop\LoopInterface
|
||||||
|
*/
|
||||||
|
public $loop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\MessageComponentInterface
|
||||||
|
*/
|
||||||
|
public $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The socket server the Ratchet Application is run off of
|
||||||
|
* @var \React\Socket\ServerInterface
|
||||||
|
*/
|
||||||
|
public $socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\MessageComponentInterface $app The Ratchet application stack to host
|
||||||
|
* @param \React\Socket\ServerInterface $socket The React socket server to run the Ratchet application off of
|
||||||
|
* @param \React\EventLoop\LoopInterface|null $loop The React looper to run the Ratchet application off of
|
||||||
|
*/
|
||||||
|
public function __construct(MessageComponentInterface $app, ServerInterface $socket, LoopInterface $loop = null) {
|
||||||
|
if (false === strpos(PHP_VERSION, "hiphop")) {
|
||||||
|
gc_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
ob_implicit_flush();
|
||||||
|
|
||||||
|
$this->loop = $loop;
|
||||||
|
$this->app = $app;
|
||||||
|
$this->socket = $socket;
|
||||||
|
|
||||||
|
$socket->on('connection', array($this, 'handleConnect'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received
|
||||||
|
* @param int $port The port to server sockets on
|
||||||
|
* @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any)
|
||||||
|
* @return IoServer
|
||||||
|
*/
|
||||||
|
public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') {
|
||||||
|
$loop = LoopFactory::create();
|
||||||
|
$socket = new Reactor($address . ':' . $port, $loop);
|
||||||
|
|
||||||
|
return new static($component, $socket, $loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the application by entering the event loop
|
||||||
|
* @throws \RuntimeException If a loop was not previously specified
|
||||||
|
*/
|
||||||
|
public function run() {
|
||||||
|
if (null === $this->loop) {
|
||||||
|
throw new \RuntimeException("A React Loop was not provided during instantiation");
|
||||||
|
}
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
$this->loop->run();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when a new connection is received from React
|
||||||
|
* @param \React\Socket\ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function handleConnect($conn) {
|
||||||
|
$conn->decor = new IoConnection($conn);
|
||||||
|
$conn->decor->resourceId = (int)$conn->stream;
|
||||||
|
|
||||||
|
$uri = $conn->getRemoteAddress();
|
||||||
|
$conn->decor->remoteAddress = trim(
|
||||||
|
parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST),
|
||||||
|
'[]'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->app->onOpen($conn->decor);
|
||||||
|
|
||||||
|
$conn->on('data', function ($data) use ($conn) {
|
||||||
|
$this->handleData($data, $conn);
|
||||||
|
});
|
||||||
|
$conn->on('close', function () use ($conn) {
|
||||||
|
$this->handleEnd($conn);
|
||||||
|
});
|
||||||
|
$conn->on('error', function (\Exception $e) use ($conn) {
|
||||||
|
$this->handleError($e, $conn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data has been received from React
|
||||||
|
* @param string $data
|
||||||
|
* @param \React\Socket\ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function handleData($data, $conn) {
|
||||||
|
try {
|
||||||
|
$this->app->onMessage($conn->decor, $data);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->handleError($e, $conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A connection has been closed by React
|
||||||
|
* @param \React\Socket\ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function handleEnd($conn) {
|
||||||
|
try {
|
||||||
|
$this->app->onClose($conn->decor);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->handleError($e, $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($conn->decor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error has occurred, let the listening application know
|
||||||
|
* @param \Exception $e
|
||||||
|
* @param \React\Socket\ConnectionInterface $conn
|
||||||
|
*/
|
||||||
|
public function handleError(\Exception $e, $conn) {
|
||||||
|
$this->app->onError($conn->decor, $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/vendor/cboden/ratchet/src/Ratchet/Server/IpBlackList.php
vendored
Normal file
111
src/vendor/cboden/ratchet/src/Ratchet/Server/IpBlackList.php
vendored
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class IpBlackList implements MessageComponentInterface {
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $_blacklist = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\MessageComponentInterface
|
||||||
|
*/
|
||||||
|
protected $_decorating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\MessageComponentInterface $component
|
||||||
|
*/
|
||||||
|
public function __construct(MessageComponentInterface $component) {
|
||||||
|
$this->_decorating = $component;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an address to the blacklist that will not be allowed to connect to your application
|
||||||
|
* @param string $ip IP address to block from connecting to your application
|
||||||
|
* @return IpBlackList
|
||||||
|
*/
|
||||||
|
public function blockAddress($ip) {
|
||||||
|
$this->_blacklist[$ip] = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unblock an address so they can access your application again
|
||||||
|
* @param string $ip IP address to unblock from connecting to your application
|
||||||
|
* @return IpBlackList
|
||||||
|
*/
|
||||||
|
public function unblockAddress($ip) {
|
||||||
|
if (isset($this->_blacklist[$this->filterAddress($ip)])) {
|
||||||
|
unset($this->_blacklist[$this->filterAddress($ip)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $address
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isBlocked($address) {
|
||||||
|
return (isset($this->_blacklist[$this->filterAddress($address)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of all the addresses blocked
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getBlockedAddresses() {
|
||||||
|
return array_keys($this->_blacklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $address
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function filterAddress($address) {
|
||||||
|
if (strstr($address, ':') && substr_count($address, '.') == 3) {
|
||||||
|
list($address, $port) = explode(':', $address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onOpen(ConnectionInterface $conn) {
|
||||||
|
if ($this->isBlocked($conn->remoteAddress)) {
|
||||||
|
return $conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_decorating->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
return $this->_decorating->onMessage($from, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onClose(ConnectionInterface $conn) {
|
||||||
|
if (!$this->isBlocked($conn->remoteAddress)) {
|
||||||
|
$this->_decorating->onClose($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
if (!$this->isBlocked($conn->remoteAddress)) {
|
||||||
|
$this->_decorating->onError($conn, $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/HandlerInterface.php
vendored
Normal file
16
src/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/HandlerInterface.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Serialize;
|
||||||
|
|
||||||
|
interface HandlerInterface {
|
||||||
|
/**
|
||||||
|
* @param array
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function serialize(array $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function unserialize($raw);
|
||||||
|
}
|
||||||
33
src/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/PhpBinaryHandler.php
vendored
Normal file
33
src/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/PhpBinaryHandler.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Serialize;
|
||||||
|
|
||||||
|
class PhpBinaryHandler implements HandlerInterface {
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function serialize(array $data) {
|
||||||
|
throw new \RuntimeException("Serialize PhpHandler:serialize code not written yet, write me!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @link http://ca2.php.net/manual/en/function.session-decode.php#108037 Code from this comment on php.net
|
||||||
|
*/
|
||||||
|
public function unserialize($raw) {
|
||||||
|
$returnData = array();
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
while ($offset < strlen($raw)) {
|
||||||
|
$num = ord($raw[$offset]);
|
||||||
|
$offset += 1;
|
||||||
|
$varname = substr($raw, $offset, $num);
|
||||||
|
$offset += $num;
|
||||||
|
$data = unserialize(substr($raw, $offset));
|
||||||
|
|
||||||
|
$returnData[$varname] = $data;
|
||||||
|
$offset += strlen(serialize($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnData;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/PhpHandler.php
vendored
Normal file
49
src/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/PhpHandler.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Serialize;
|
||||||
|
|
||||||
|
class PhpHandler implements HandlerInterface {
|
||||||
|
/**
|
||||||
|
* Simply reverse behaviour of unserialize method.
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function serialize(array $data) {
|
||||||
|
$preSerialized = array();
|
||||||
|
$serialized = '';
|
||||||
|
|
||||||
|
if (count($data)) {
|
||||||
|
foreach ($data as $bucket => $bucketData) {
|
||||||
|
$preSerialized[] = $bucket . '|' . serialize($bucketData);
|
||||||
|
}
|
||||||
|
$serialized = implode('', $preSerialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @link http://ca2.php.net/manual/en/function.session-decode.php#108037 Code from this comment on php.net
|
||||||
|
* @throws \UnexpectedValueException If there is a problem parsing the data
|
||||||
|
*/
|
||||||
|
public function unserialize($raw) {
|
||||||
|
$returnData = array();
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
while ($offset < strlen($raw)) {
|
||||||
|
if (!strstr(substr($raw, $offset), "|")) {
|
||||||
|
throw new \UnexpectedValueException("invalid data, remaining: " . substr($raw, $offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
$pos = strpos($raw, "|", $offset);
|
||||||
|
$num = $pos - $offset;
|
||||||
|
$varname = substr($raw, $offset, $num);
|
||||||
|
$offset += $num + 1;
|
||||||
|
$data = unserialize(substr($raw, $offset));
|
||||||
|
|
||||||
|
$returnData[$varname] = $data;
|
||||||
|
$offset += strlen(serialize($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnData;
|
||||||
|
}
|
||||||
|
}
|
||||||
243
src/vendor/cboden/ratchet/src/Ratchet/Session/SessionProvider.php
vendored
Normal file
243
src/vendor/cboden/ratchet/src/Ratchet/Session/SessionProvider.php
vendored
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\Http\HttpServerInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Ratchet\Session\Storage\VirtualSessionStorage;
|
||||||
|
use Ratchet\Session\Serialize\HandlerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component will allow access to session data from your website for each user connected
|
||||||
|
* Symfony HttpFoundation is required for this component to work
|
||||||
|
* Your website must also use Symfony HttpFoundation Sessions to read your sites session data
|
||||||
|
* If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer)
|
||||||
|
*/
|
||||||
|
class SessionProvider implements HttpServerInterface {
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\MessageComponentInterface
|
||||||
|
*/
|
||||||
|
protected $_app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected handler storage assigned by the developer
|
||||||
|
* @var \SessionHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null storage handler if no previous session was found
|
||||||
|
* @var \SessionHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $_null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\Session\Serialize\HandlerInterface
|
||||||
|
*/
|
||||||
|
protected $_serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\Http\HttpServerInterface $app
|
||||||
|
* @param \SessionHandlerInterface $handler
|
||||||
|
* @param array $options
|
||||||
|
* @param \Ratchet\Session\Serialize\HandlerInterface $serializer
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function __construct(HttpServerInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) {
|
||||||
|
$this->_app = $app;
|
||||||
|
$this->_handler = $handler;
|
||||||
|
$this->_null = new NullSessionHandler;
|
||||||
|
|
||||||
|
ini_set('session.auto_start', 0);
|
||||||
|
ini_set('session.cache_limiter', '');
|
||||||
|
ini_set('session.use_cookies', 0);
|
||||||
|
|
||||||
|
$this->setOptions($options);
|
||||||
|
|
||||||
|
if (null === $serializer) {
|
||||||
|
$serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh?
|
||||||
|
if (!class_exists($serialClass)) {
|
||||||
|
throw new \RuntimeException('Unable to parse session serialize handler');
|
||||||
|
}
|
||||||
|
|
||||||
|
$serializer = new $serialClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_serializer = $serializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
|
$sessionName = ini_get('session.name');
|
||||||
|
|
||||||
|
$id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) {
|
||||||
|
if ($accumulator) {
|
||||||
|
return $accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
$crumbs = $this->parseCookie($cookie);
|
||||||
|
|
||||||
|
return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
if (null === $request || false === $id) {
|
||||||
|
$saveHandler = $this->_null;
|
||||||
|
$id = '';
|
||||||
|
} else {
|
||||||
|
$saveHandler = $this->_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->Session = new Session(new VirtualSessionStorage($saveHandler, $id, $this->_serializer));
|
||||||
|
|
||||||
|
if (ini_get('session.auto_start')) {
|
||||||
|
$conn->Session->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_app->onOpen($conn, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
return $this->_app->onMessage($from, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onClose(ConnectionInterface $conn) {
|
||||||
|
// "close" session for Connection
|
||||||
|
|
||||||
|
return $this->_app->onClose($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
return $this->_app->onError($conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all the php session. ini options
|
||||||
|
* © Symfony
|
||||||
|
* @param array $options
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function setOptions(array $options) {
|
||||||
|
$all = array(
|
||||||
|
'auto_start', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
|
||||||
|
'cookie_lifetime', 'cookie_path', 'cookie_secure',
|
||||||
|
'entropy_file', 'entropy_length', 'gc_divisor',
|
||||||
|
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
|
||||||
|
'hash_function', 'name', 'referer_check',
|
||||||
|
'serialize_handler', 'use_cookies',
|
||||||
|
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
|
||||||
|
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
|
||||||
|
'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($all as $key) {
|
||||||
|
if (!array_key_exists($key, $options)) {
|
||||||
|
$options[$key] = ini_get("session.{$key}");
|
||||||
|
} else {
|
||||||
|
ini_set("session.{$key}", $options[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $langDef Input to convert
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function toClassCase($langDef) {
|
||||||
|
return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken from Guzzle3
|
||||||
|
*/
|
||||||
|
private static $cookieParts = array(
|
||||||
|
'domain' => 'Domain',
|
||||||
|
'path' => 'Path',
|
||||||
|
'max_age' => 'Max-Age',
|
||||||
|
'expires' => 'Expires',
|
||||||
|
'version' => 'Version',
|
||||||
|
'secure' => 'Secure',
|
||||||
|
'port' => 'Port',
|
||||||
|
'discard' => 'Discard',
|
||||||
|
'comment' => 'Comment',
|
||||||
|
'comment_url' => 'Comment-Url',
|
||||||
|
'http_only' => 'HttpOnly'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken from Guzzle3
|
||||||
|
*/
|
||||||
|
private function parseCookie($cookie, $host = null, $path = null, $decode = false) {
|
||||||
|
// Explode the cookie string using a series of semicolons
|
||||||
|
$pieces = array_filter(array_map('trim', explode(';', $cookie)));
|
||||||
|
|
||||||
|
// The name of the cookie (first kvp) must include an equal sign.
|
||||||
|
if (empty($pieces) || !strpos($pieces[0], '=')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the default return array
|
||||||
|
$data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array(
|
||||||
|
'cookies' => array(),
|
||||||
|
'data' => array(),
|
||||||
|
'path' => $path ?: '/',
|
||||||
|
'http_only' => false,
|
||||||
|
'discard' => false,
|
||||||
|
'domain' => $host
|
||||||
|
));
|
||||||
|
$foundNonCookies = 0;
|
||||||
|
|
||||||
|
// Add the cookie pieces into the parsed data array
|
||||||
|
foreach ($pieces as $part) {
|
||||||
|
|
||||||
|
$cookieParts = explode('=', $part, 2);
|
||||||
|
$key = trim($cookieParts[0]);
|
||||||
|
|
||||||
|
if (count($cookieParts) == 1) {
|
||||||
|
// Can be a single value (e.g. secure, httpOnly)
|
||||||
|
$value = true;
|
||||||
|
} else {
|
||||||
|
// Be sure to strip wrapping quotes
|
||||||
|
$value = trim($cookieParts[1], " \n\r\t\0\x0B\"");
|
||||||
|
if ($decode) {
|
||||||
|
$value = urldecode($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only check for non-cookies when cookies have been found
|
||||||
|
if (!empty($data['cookies'])) {
|
||||||
|
foreach (self::$cookieParts as $mapValue => $search) {
|
||||||
|
if (!strcasecmp($search, $key)) {
|
||||||
|
$data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value;
|
||||||
|
$foundNonCookies++;
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a
|
||||||
|
// cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data.
|
||||||
|
$data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the expires date
|
||||||
|
if (!$data['expires'] && $data['max_age']) {
|
||||||
|
$data['expires'] = time() + (int) $data['max_age'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/vendor/cboden/ratchet/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php
vendored
Normal file
54
src/vendor/cboden/ratchet/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Storage\Proxy;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||||
|
|
||||||
|
class VirtualProxy extends SessionHandlerProxy {
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_sessionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_sessionName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function __construct(\SessionHandlerInterface $handler) {
|
||||||
|
parent::__construct($handler);
|
||||||
|
|
||||||
|
$this->saveHandlerName = 'user';
|
||||||
|
$this->_sessionName = ini_get('session.name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getId() {
|
||||||
|
return $this->_sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setId($id) {
|
||||||
|
$this->_sessionId = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName() {
|
||||||
|
return $this->_sessionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DO NOT CALL THIS METHOD
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function setName($name) {
|
||||||
|
throw new \RuntimeException("Can not change session name in VirtualProxy");
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/vendor/cboden/ratchet/src/Ratchet/Session/Storage/VirtualSessionStorage.php
vendored
Normal file
88
src/vendor/cboden/ratchet/src/Ratchet/Session/Storage/VirtualSessionStorage.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Storage;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
|
use Ratchet\Session\Storage\Proxy\VirtualProxy;
|
||||||
|
use Ratchet\Session\Serialize\HandlerInterface;
|
||||||
|
|
||||||
|
class VirtualSessionStorage extends NativeSessionStorage {
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\Session\Serialize\HandlerInterface
|
||||||
|
*/
|
||||||
|
protected $_serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \SessionHandlerInterface $handler
|
||||||
|
* @param string $sessionId The ID of the session to retrieve
|
||||||
|
* @param \Ratchet\Session\Serialize\HandlerInterface $serializer
|
||||||
|
*/
|
||||||
|
public function __construct(\SessionHandlerInterface $handler, $sessionId, HandlerInterface $serializer) {
|
||||||
|
$this->setSaveHandler($handler);
|
||||||
|
$this->saveHandler->setId($sessionId);
|
||||||
|
$this->_serializer = $serializer;
|
||||||
|
$this->setMetadataBag(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start() {
|
||||||
|
if ($this->started && !$this->closed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You have to call Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::open() to use
|
||||||
|
// pdo_sqlite (and possible pdo_*) as session storage, if you are using a DSN string instead of a \PDO object
|
||||||
|
// in the constructor. The method arguments are filled with the values, which are also used by the symfony
|
||||||
|
// framework in this case. This must not be the best choice, but it works.
|
||||||
|
$this->saveHandler->open(session_save_path(), session_name());
|
||||||
|
|
||||||
|
$rawData = $this->saveHandler->read($this->saveHandler->getId());
|
||||||
|
$sessionData = $this->_serializer->unserialize($rawData);
|
||||||
|
|
||||||
|
$this->loadSession($sessionData);
|
||||||
|
|
||||||
|
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
|
||||||
|
$this->saveHandler->setActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function regenerate($destroy = false, $lifetime = null) {
|
||||||
|
// .. ?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function save() {
|
||||||
|
// get the data from the bags?
|
||||||
|
// serialize the data
|
||||||
|
// save the data using the saveHandler
|
||||||
|
// $this->saveHandler->write($this->saveHandler->getId(),
|
||||||
|
|
||||||
|
if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) {
|
||||||
|
$this->saveHandler->setActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setSaveHandler($saveHandler = null) {
|
||||||
|
if (!($saveHandler instanceof \SessionHandlerInterface)) {
|
||||||
|
throw new \InvalidArgumentException('Handler must be instance of SessionHandlerInterface');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($saveHandler instanceof VirtualProxy)) {
|
||||||
|
$saveHandler = new VirtualProxy($saveHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveHandler = $saveHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/vendor/cboden/ratchet/src/Ratchet/Wamp/Exception.php
vendored
Normal file
5
src/vendor/cboden/ratchet/src/Ratchet/Wamp/Exception.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
|
||||||
|
class Exception extends \Exception {
|
||||||
|
}
|
||||||
31
src/vendor/cboden/ratchet/src/Ratchet/Wamp/JsonException.php
vendored
Normal file
31
src/vendor/cboden/ratchet/src/Ratchet/Wamp/JsonException.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
|
||||||
|
class JsonException extends Exception {
|
||||||
|
public function __construct() {
|
||||||
|
$code = json_last_error();
|
||||||
|
|
||||||
|
switch ($code) {
|
||||||
|
case JSON_ERROR_DEPTH:
|
||||||
|
$msg = 'Maximum stack depth exceeded';
|
||||||
|
break;
|
||||||
|
case JSON_ERROR_STATE_MISMATCH:
|
||||||
|
$msg = 'Underflow or the modes mismatch';
|
||||||
|
break;
|
||||||
|
case JSON_ERROR_CTRL_CHAR:
|
||||||
|
$msg = 'Unexpected control character found';
|
||||||
|
break;
|
||||||
|
case JSON_ERROR_SYNTAX:
|
||||||
|
$msg = 'Syntax error, malformed JSON';
|
||||||
|
break;
|
||||||
|
case JSON_ERROR_UTF8:
|
||||||
|
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$msg = 'Unknown error';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($msg, $code);
|
||||||
|
}
|
||||||
|
}
|
||||||
161
src/vendor/cboden/ratchet/src/Ratchet/Wamp/ServerProtocol.php
vendored
Normal file
161
src/vendor/cboden/ratchet/src/Ratchet/Wamp/ServerProtocol.php
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket Application Messaging Protocol
|
||||||
|
*
|
||||||
|
* @link http://wamp.ws/spec
|
||||||
|
* @link https://github.com/oberstet/autobahn-js
|
||||||
|
*
|
||||||
|
* +--------------+----+------------------+
|
||||||
|
* | Message Type | ID | DIRECTION |
|
||||||
|
* |--------------+----+------------------+
|
||||||
|
* | WELCOME | 0 | Server-to-Client |
|
||||||
|
* | PREFIX | 1 | Bi-Directional |
|
||||||
|
* | CALL | 2 | Client-to-Server |
|
||||||
|
* | CALL RESULT | 3 | Server-to-Client |
|
||||||
|
* | CALL ERROR | 4 | Server-to-Client |
|
||||||
|
* | SUBSCRIBE | 5 | Client-to-Server |
|
||||||
|
* | UNSUBSCRIBE | 6 | Client-to-Server |
|
||||||
|
* | PUBLISH | 7 | Client-to-Server |
|
||||||
|
* | EVENT | 8 | Server-to-Client |
|
||||||
|
* +--------------+----+------------------+
|
||||||
|
*/
|
||||||
|
class ServerProtocol implements MessageComponentInterface, WsServerInterface {
|
||||||
|
const MSG_WELCOME = 0;
|
||||||
|
const MSG_PREFIX = 1;
|
||||||
|
const MSG_CALL = 2;
|
||||||
|
const MSG_CALL_RESULT = 3;
|
||||||
|
const MSG_CALL_ERROR = 4;
|
||||||
|
const MSG_SUBSCRIBE = 5;
|
||||||
|
const MSG_UNSUBSCRIBE = 6;
|
||||||
|
const MSG_PUBLISH = 7;
|
||||||
|
const MSG_EVENT = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var WampServerInterface
|
||||||
|
*/
|
||||||
|
protected $_decorating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \SplObjectStorage
|
||||||
|
*/
|
||||||
|
protected $connections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param WampServerInterface $serverComponent An class to propagate calls through
|
||||||
|
*/
|
||||||
|
public function __construct(WampServerInterface $serverComponent) {
|
||||||
|
$this->_decorating = $serverComponent;
|
||||||
|
$this->connections = new \SplObjectStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSubProtocols() {
|
||||||
|
if ($this->_decorating instanceof WsServerInterface) {
|
||||||
|
$subs = $this->_decorating->getSubProtocols();
|
||||||
|
$subs[] = 'wamp';
|
||||||
|
|
||||||
|
return $subs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['wamp'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$decor = new WampConnection($conn);
|
||||||
|
$this->connections->attach($conn, $decor);
|
||||||
|
|
||||||
|
$this->_decorating->onOpen($decor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @throws \Ratchet\Wamp\Exception
|
||||||
|
* @throws \Ratchet\Wamp\JsonException
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
$from = $this->connections[$from];
|
||||||
|
|
||||||
|
if (null === ($json = @json_decode($msg, true))) {
|
||||||
|
throw new JsonException;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($json) || $json !== array_values($json)) {
|
||||||
|
throw new Exception("Invalid WAMP message format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))) {
|
||||||
|
throw new Exception('Invalid Topic, must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($json[0]) {
|
||||||
|
case static::MSG_PREFIX:
|
||||||
|
$from->WAMP->prefixes[$json[1]] = $json[2];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static::MSG_CALL:
|
||||||
|
array_shift($json);
|
||||||
|
$callID = array_shift($json);
|
||||||
|
$procURI = array_shift($json);
|
||||||
|
|
||||||
|
if (count($json) == 1 && is_array($json[0])) {
|
||||||
|
$json = $json[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_decorating->onCall($from, $callID, $from->getUri($procURI), $json);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static::MSG_SUBSCRIBE:
|
||||||
|
$this->_decorating->onSubscribe($from, $from->getUri($json[1]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static::MSG_UNSUBSCRIBE:
|
||||||
|
$this->_decorating->onUnSubscribe($from, $from->getUri($json[1]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static::MSG_PUBLISH:
|
||||||
|
$exclude = (array_key_exists(3, $json) ? $json[3] : null);
|
||||||
|
if (!is_array($exclude)) {
|
||||||
|
if (true === (boolean)$exclude) {
|
||||||
|
$exclude = [$from->WAMP->sessionId];
|
||||||
|
} else {
|
||||||
|
$exclude = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$eligible = (array_key_exists(4, $json) ? $json[4] : []);
|
||||||
|
|
||||||
|
$this->_decorating->onPublish($from, $from->getUri($json[1]), $json[2], $exclude, $eligible);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Invalid WAMP message type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
$decor = $this->connections[$conn];
|
||||||
|
$this->connections->detach($conn);
|
||||||
|
|
||||||
|
$this->_decorating->onClose($decor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
return $this->_decorating->onError($this->connections[$conn], $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/vendor/cboden/ratchet/src/Ratchet/Wamp/Topic.php
vendored
Normal file
101
src/vendor/cboden/ratchet/src/Ratchet/Wamp/Topic.php
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A topic/channel containing connections that have subscribed to it
|
||||||
|
*/
|
||||||
|
class Topic implements \IteratorAggregate, \Countable {
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
private $subscribers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $topicId Unique ID for this object
|
||||||
|
*/
|
||||||
|
public function __construct($topicId) {
|
||||||
|
$this->id = $topicId;
|
||||||
|
$this->subscribers = new \SplObjectStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId() {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to all the connections in this topic
|
||||||
|
* @param string|array $msg Payload to publish
|
||||||
|
* @param array $exclude A list of session IDs the message should be excluded from (blacklist)
|
||||||
|
* @param array $eligible A list of session Ids the message should be send to (whitelist)
|
||||||
|
* @return Topic The same Topic object to chain
|
||||||
|
*/
|
||||||
|
public function broadcast($msg, array $exclude = array(), array $eligible = array()) {
|
||||||
|
$useEligible = (bool)count($eligible);
|
||||||
|
foreach ($this->subscribers as $client) {
|
||||||
|
if (in_array($client->WAMP->sessionId, $exclude)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($useEligible && !in_array($client->WAMP->sessionId, $eligible)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client->event($this->id, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param WampConnection $conn
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function has(ConnectionInterface $conn) {
|
||||||
|
return $this->subscribers->contains($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param WampConnection $conn
|
||||||
|
* @return Topic
|
||||||
|
*/
|
||||||
|
public function add(ConnectionInterface $conn) {
|
||||||
|
$this->subscribers->attach($conn);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param WampConnection $conn
|
||||||
|
* @return Topic
|
||||||
|
*/
|
||||||
|
public function remove(ConnectionInterface $conn) {
|
||||||
|
if ($this->subscribers->contains($conn)) {
|
||||||
|
$this->subscribers->detach($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function getIterator() {
|
||||||
|
return $this->subscribers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function count() {
|
||||||
|
return $this->subscribers->count();
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/vendor/cboden/ratchet/src/Ratchet/Wamp/TopicManager.php
vendored
Normal file
125
src/vendor/cboden/ratchet/src/Ratchet/Wamp/TopicManager.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
|
||||||
|
class TopicManager implements WsServerInterface, WampServerInterface {
|
||||||
|
/**
|
||||||
|
* @var WampServerInterface
|
||||||
|
*/
|
||||||
|
protected $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $topicLookup = array();
|
||||||
|
|
||||||
|
public function __construct(WampServerInterface $app) {
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$conn->WAMP->subscriptions = new \SplObjectStorage;
|
||||||
|
$this->app->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {
|
||||||
|
$this->app->onCall($conn, $id, $this->getTopic($topic), $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onSubscribe(ConnectionInterface $conn, $topic) {
|
||||||
|
$topicObj = $this->getTopic($topic);
|
||||||
|
|
||||||
|
if ($conn->WAMP->subscriptions->contains($topicObj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->topicLookup[$topic]->add($conn);
|
||||||
|
$conn->WAMP->subscriptions->attach($topicObj);
|
||||||
|
$this->app->onSubscribe($conn, $topicObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onUnsubscribe(ConnectionInterface $conn, $topic) {
|
||||||
|
$topicObj = $this->getTopic($topic);
|
||||||
|
|
||||||
|
if (!$conn->WAMP->subscriptions->contains($topicObj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cleanTopic($topicObj, $conn);
|
||||||
|
|
||||||
|
$this->app->onUnsubscribe($conn, $topicObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
|
||||||
|
$this->app->onPublish($conn, $this->getTopic($topic), $event, $exclude, $eligible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
$this->app->onClose($conn);
|
||||||
|
|
||||||
|
foreach ($this->topicLookup as $topic) {
|
||||||
|
$this->cleanTopic($topic, $conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$this->app->onError($conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSubProtocols() {
|
||||||
|
if ($this->app instanceof WsServerInterface) {
|
||||||
|
return $this->app->getSubProtocols();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return Topic
|
||||||
|
*/
|
||||||
|
protected function getTopic($topic) {
|
||||||
|
if (!array_key_exists($topic, $this->topicLookup)) {
|
||||||
|
$this->topicLookup[$topic] = new Topic($topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->topicLookup[$topic];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cleanTopic(Topic $topic, ConnectionInterface $conn) {
|
||||||
|
if ($conn->WAMP->subscriptions->contains($topic)) {
|
||||||
|
$conn->WAMP->subscriptions->detach($topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->topicLookup[$topic->getId()]->remove($conn);
|
||||||
|
|
||||||
|
if (0 === $topic->count()) {
|
||||||
|
unset($this->topicLookup[$topic->getId()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/vendor/cboden/ratchet/src/Ratchet/Wamp/WampConnection.php
vendored
Normal file
115
src/vendor/cboden/ratchet/src/Ratchet/Wamp/WampConnection.php
vendored
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\AbstractConnectionDecorator;
|
||||||
|
use Ratchet\Wamp\ServerProtocol as WAMP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ConnectionInterface object wrapper that is passed to your WAMP application
|
||||||
|
* representing a client. Methods on this Connection are therefore different.
|
||||||
|
* @property \stdClass $WAMP
|
||||||
|
*/
|
||||||
|
class WampConnection extends AbstractConnectionDecorator {
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function __construct(ConnectionInterface $conn) {
|
||||||
|
parent::__construct($conn);
|
||||||
|
|
||||||
|
$this->WAMP = new \StdClass;
|
||||||
|
$this->WAMP->sessionId = str_replace('.', '', uniqid(mt_rand(), true));
|
||||||
|
$this->WAMP->prefixes = array();
|
||||||
|
|
||||||
|
$this->send(json_encode(array(WAMP::MSG_WELCOME, $this->WAMP->sessionId, 1, \Ratchet\VERSION)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Successfully respond to a call made by the client
|
||||||
|
* @param string $id The unique ID given by the client to respond to
|
||||||
|
* @param array $data an object or array
|
||||||
|
* @return WampConnection
|
||||||
|
*/
|
||||||
|
public function callResult($id, $data = array()) {
|
||||||
|
return $this->send(json_encode(array(WAMP::MSG_CALL_RESULT, $id, $data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond with an error to a client call
|
||||||
|
* @param string $id The unique ID given by the client to respond to
|
||||||
|
* @param string $errorUri The URI given to identify the specific error
|
||||||
|
* @param string $desc A developer-oriented description of the error
|
||||||
|
* @param string $details An optional human readable detail message to send back
|
||||||
|
* @return WampConnection
|
||||||
|
*/
|
||||||
|
public function callError($id, $errorUri, $desc = '', $details = null) {
|
||||||
|
if ($errorUri instanceof Topic) {
|
||||||
|
$errorUri = (string)$errorUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array(WAMP::MSG_CALL_ERROR, $id, $errorUri, $desc);
|
||||||
|
|
||||||
|
if (null !== $details) {
|
||||||
|
$data[] = $details;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->send(json_encode($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $topic The topic to broadcast to
|
||||||
|
* @param mixed $msg Data to send with the event. Anything that is json'able
|
||||||
|
* @return WampConnection
|
||||||
|
*/
|
||||||
|
public function event($topic, $msg) {
|
||||||
|
return $this->send(json_encode(array(WAMP::MSG_EVENT, (string)$topic, $msg)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $curie
|
||||||
|
* @param string $uri
|
||||||
|
* @return WampConnection
|
||||||
|
*/
|
||||||
|
public function prefix($curie, $uri) {
|
||||||
|
$this->WAMP->prefixes[$curie] = (string)$uri;
|
||||||
|
|
||||||
|
return $this->send(json_encode(array(WAMP::MSG_PREFIX, $curie, (string)$uri)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full request URI from the connection object if a prefix has been established for it
|
||||||
|
* @param string $uri
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUri($uri) {
|
||||||
|
$curieSeperator = ':';
|
||||||
|
|
||||||
|
if (preg_match('/http(s*)\:\/\//', $uri) == false) {
|
||||||
|
if (strpos($uri, $curieSeperator) !== false) {
|
||||||
|
list($prefix, $action) = explode($curieSeperator, $uri);
|
||||||
|
|
||||||
|
if(isset($this->WAMP->prefixes[$prefix]) === true){
|
||||||
|
return $this->WAMP->prefixes[$prefix] . '#' . $action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function send($data) {
|
||||||
|
$this->getConnection()->send($data);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function close($opt = null) {
|
||||||
|
$this->getConnection()->close($opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
67
src/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServer.php
vendored
Normal file
67
src/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServer.php
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable support for the official WAMP sub-protocol in your application
|
||||||
|
* WAMP allows for Pub/Sub and RPC
|
||||||
|
* @link http://wamp.ws The WAMP specification
|
||||||
|
* @link https://github.com/oberstet/autobahn-js Souce for client side library
|
||||||
|
* @link http://autobahn.s3.amazonaws.com/js/autobahn.min.js Minified client side library
|
||||||
|
*/
|
||||||
|
class WampServer implements MessageComponentInterface, WsServerInterface {
|
||||||
|
/**
|
||||||
|
* @var ServerProtocol
|
||||||
|
*/
|
||||||
|
protected $wampProtocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class just makes it 1 step easier to use Topic objects in WAMP
|
||||||
|
* If you're looking at the source code, look in the __construct of this
|
||||||
|
* class and use that to make your application instead of using this
|
||||||
|
*/
|
||||||
|
public function __construct(WampServerInterface $app) {
|
||||||
|
$this->wampProtocol = new ServerProtocol(new TopicManager($app));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$this->wampProtocol->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $conn, $msg) {
|
||||||
|
try {
|
||||||
|
$this->wampProtocol->onMessage($conn, $msg);
|
||||||
|
} catch (Exception $we) {
|
||||||
|
$conn->close(1007);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
$this->wampProtocol->onClose($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$this->wampProtocol->onError($conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSubProtocols() {
|
||||||
|
return $this->wampProtocol->getSubProtocols();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServerInterface.php
vendored
Normal file
43
src/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServerInterface.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\ComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension of Ratchet\ComponentInterface to server a WAMP application
|
||||||
|
* onMessage is replaced by various types of messages for this protocol (pub/sub or rpc)
|
||||||
|
*/
|
||||||
|
interface WampServerInterface extends ComponentInterface {
|
||||||
|
/**
|
||||||
|
* An RPC call has been received
|
||||||
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
|
* @param string $id The unique ID of the RPC, required to respond to
|
||||||
|
* @param string|Topic $topic The topic to execute the call against
|
||||||
|
* @param array $params Call parameters received from the client
|
||||||
|
*/
|
||||||
|
function onCall(ConnectionInterface $conn, $id, $topic, array $params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to subscribe to a topic has been made
|
||||||
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
|
* @param string|Topic $topic The topic to subscribe to
|
||||||
|
*/
|
||||||
|
function onSubscribe(ConnectionInterface $conn, $topic);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to unsubscribe from a topic has been made
|
||||||
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
|
* @param string|Topic $topic The topic to unsubscribe from
|
||||||
|
*/
|
||||||
|
function onUnSubscribe(ConnectionInterface $conn, $topic);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client is attempting to publish content to a subscribed connections on a URI
|
||||||
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
|
* @param string|Topic $topic The topic the user has attempted to publish to
|
||||||
|
* @param string $event Payload of the publish
|
||||||
|
* @param array $exclude A list of session IDs the message should be excluded from (blacklist)
|
||||||
|
* @param array $eligible A list of session Ids the message should be send to (whitelist)
|
||||||
|
*/
|
||||||
|
function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible);
|
||||||
|
}
|
||||||
20
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/ConnContext.php
vendored
Normal file
20
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/ConnContext.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\RFC6455\Messaging\MessageBuffer;
|
||||||
|
|
||||||
|
class ConnContext {
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\WebSocket\WsConnection
|
||||||
|
*/
|
||||||
|
public $connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\RFC6455\Messaging\MessageBuffer;
|
||||||
|
*/
|
||||||
|
public $buffer;
|
||||||
|
|
||||||
|
public function __construct(WsConnection $conn, MessageBuffer $buffer) {
|
||||||
|
$this->connection = $conn;
|
||||||
|
$this->buffer = $buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageCallableInterface.php
vendored
Normal file
8
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageCallableInterface.php
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\RFC6455\Messaging\MessageInterface;
|
||||||
|
|
||||||
|
interface MessageCallableInterface {
|
||||||
|
public function onMessage(ConnectionInterface $conn, MessageInterface $msg);
|
||||||
|
}
|
||||||
6
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageComponentInterface.php
vendored
Normal file
6
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageComponentInterface.php
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\ComponentInterface;
|
||||||
|
|
||||||
|
interface MessageComponentInterface extends ComponentInterface, MessageCallableInterface {
|
||||||
|
}
|
||||||
45
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsConnection.php
vendored
Normal file
45
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsConnection.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\AbstractConnectionDecorator;
|
||||||
|
use Ratchet\RFC6455\Messaging\DataInterface;
|
||||||
|
use Ratchet\RFC6455\Messaging\Frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @property \StdClass $WebSocket
|
||||||
|
*/
|
||||||
|
class WsConnection extends AbstractConnectionDecorator {
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($msg) {
|
||||||
|
if (!$this->WebSocket->closing) {
|
||||||
|
if (!($msg instanceof DataInterface)) {
|
||||||
|
$msg = new Frame($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getConnection()->send($msg->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|\Ratchet\RFC6455\Messaging\DataInterface
|
||||||
|
*/
|
||||||
|
public function close($code = 1000) {
|
||||||
|
if ($this->WebSocket->closing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code instanceof DataInterface) {
|
||||||
|
$this->send($code);
|
||||||
|
} else {
|
||||||
|
$this->send(new Frame(pack('n', $code), true, Frame::OP_CLOSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getConnection()->close();
|
||||||
|
|
||||||
|
$this->WebSocket->closing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
225
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServer.php
vendored
Normal file
225
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServer.php
vendored
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\ComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\MessageComponentInterface as DataComponentInterface;
|
||||||
|
use Ratchet\Http\HttpServerInterface;
|
||||||
|
use Ratchet\Http\CloseResponseTrait;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Ratchet\RFC6455\Messaging\MessageInterface;
|
||||||
|
use Ratchet\RFC6455\Messaging\FrameInterface;
|
||||||
|
use Ratchet\RFC6455\Messaging\Frame;
|
||||||
|
use Ratchet\RFC6455\Messaging\MessageBuffer;
|
||||||
|
use Ratchet\RFC6455\Messaging\CloseFrameChecker;
|
||||||
|
use Ratchet\RFC6455\Handshake\ServerNegotiator;
|
||||||
|
use Ratchet\RFC6455\Handshake\RequestVerifier;
|
||||||
|
use React\EventLoop\LoopInterface;
|
||||||
|
use GuzzleHttp\Psr7\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter to handle WebSocket requests/responses
|
||||||
|
* This is a mediator between the Server and your application to handle real-time messaging through a web browser
|
||||||
|
* @link http://ca.php.net/manual/en/ref.http.php
|
||||||
|
* @link http://dev.w3.org/html5/websockets/
|
||||||
|
*/
|
||||||
|
class WsServer implements HttpServerInterface {
|
||||||
|
use CloseResponseTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorated component
|
||||||
|
* @var \Ratchet\ComponentInterface
|
||||||
|
*/
|
||||||
|
private $delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \SplObjectStorage
|
||||||
|
*/
|
||||||
|
protected $connections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\RFC6455\Messaging\CloseFrameChecker
|
||||||
|
*/
|
||||||
|
private $closeFrameChecker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\RFC6455\Handshake\ServerNegotiator
|
||||||
|
*/
|
||||||
|
private $handshakeNegotiator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Closure
|
||||||
|
*/
|
||||||
|
private $ueFlowFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Closure
|
||||||
|
*/
|
||||||
|
private $pongReceiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Closure
|
||||||
|
*/
|
||||||
|
private $msgCb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Ratchet\WebSocket\MessageComponentInterface|\Ratchet\MessageComponentInterface $component Your application to run with WebSockets
|
||||||
|
* @note If you want to enable sub-protocols have your component implement WsServerInterface as well
|
||||||
|
*/
|
||||||
|
public function __construct(ComponentInterface $component) {
|
||||||
|
if ($component instanceof MessageComponentInterface) {
|
||||||
|
$this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) {
|
||||||
|
$this->delegate->onMessage($conn, $msg);
|
||||||
|
};
|
||||||
|
} elseif ($component instanceof DataComponentInterface) {
|
||||||
|
$this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) {
|
||||||
|
$this->delegate->onMessage($conn, $msg->getPayload());
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bin2hex('✓') !== 'e29c93') {
|
||||||
|
throw new \DomainException('Bad encoding, unicode character ✓ did not match expected value. Ensure charset UTF-8 and check ini val mbstring.func_autoload');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->delegate = $component;
|
||||||
|
$this->connections = new \SplObjectStorage;
|
||||||
|
|
||||||
|
$this->closeFrameChecker = new CloseFrameChecker;
|
||||||
|
$this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier);
|
||||||
|
$this->handshakeNegotiator->setStrictSubProtocolCheck(true);
|
||||||
|
|
||||||
|
if ($component instanceof WsServerInterface) {
|
||||||
|
$this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pongReceiver = function() {};
|
||||||
|
|
||||||
|
$reusableUnderflowException = new \UnderflowException;
|
||||||
|
$this->ueFlowFactory = function() use ($reusableUnderflowException) {
|
||||||
|
return $reusableUnderflowException;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
|
if (null === $request) {
|
||||||
|
throw new \UnexpectedValueException('$request can not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->httpRequest = $request;
|
||||||
|
|
||||||
|
$conn->WebSocket = new \StdClass;
|
||||||
|
$conn->WebSocket->closing = false;
|
||||||
|
|
||||||
|
$response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION);
|
||||||
|
|
||||||
|
$conn->send(Message::toString($response));
|
||||||
|
|
||||||
|
if (101 !== $response->getStatusCode()) {
|
||||||
|
return $conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$wsConn = new WsConnection($conn);
|
||||||
|
|
||||||
|
$streamer = new MessageBuffer(
|
||||||
|
$this->closeFrameChecker,
|
||||||
|
function(MessageInterface $msg) use ($wsConn) {
|
||||||
|
$cb = $this->msgCb;
|
||||||
|
$cb($wsConn, $msg);
|
||||||
|
},
|
||||||
|
function(FrameInterface $frame) use ($wsConn) {
|
||||||
|
$this->onControlFrame($frame, $wsConn);
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
$this->ueFlowFactory
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->connections->attach($conn, new ConnContext($wsConn, $streamer));
|
||||||
|
|
||||||
|
return $this->delegate->onOpen($wsConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
if ($from->WebSocket->closing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connections[$from]->buffer->onData($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
if ($this->connections->contains($conn)) {
|
||||||
|
$context = $this->connections[$conn];
|
||||||
|
$this->connections->detach($conn);
|
||||||
|
|
||||||
|
$this->delegate->onClose($context->connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
if ($this->connections->contains($conn)) {
|
||||||
|
$this->delegate->onError($this->connections[$conn]->connection, $e);
|
||||||
|
} else {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onControlFrame(FrameInterface $frame, WsConnection $conn) {
|
||||||
|
switch ($frame->getOpCode()) {
|
||||||
|
case Frame::OP_CLOSE:
|
||||||
|
$conn->close($frame);
|
||||||
|
break;
|
||||||
|
case Frame::OP_PING:
|
||||||
|
$conn->send(new Frame($frame->getPayload(), true, Frame::OP_PONG));
|
||||||
|
break;
|
||||||
|
case Frame::OP_PONG:
|
||||||
|
$pongReceiver = $this->pongReceiver;
|
||||||
|
$pongReceiver($frame, $conn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStrictSubProtocolCheck($enable) {
|
||||||
|
$this->handshakeNegotiator->setStrictSubProtocolCheck($enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enableKeepAlive(LoopInterface $loop, $interval = 30) {
|
||||||
|
$lastPing = new Frame(uniqid(), true, Frame::OP_PING);
|
||||||
|
$pingedConnections = new \SplObjectStorage;
|
||||||
|
$splClearer = new \SplObjectStorage;
|
||||||
|
|
||||||
|
$this->pongReceiver = function(FrameInterface $frame, $wsConn) use ($pingedConnections, &$lastPing) {
|
||||||
|
if ($frame->getPayload() === $lastPing->getPayload()) {
|
||||||
|
$pingedConnections->detach($wsConn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$loop->addPeriodicTimer((int)$interval, function() use ($pingedConnections, &$lastPing, $splClearer) {
|
||||||
|
foreach ($pingedConnections as $wsConn) {
|
||||||
|
$wsConn->close();
|
||||||
|
}
|
||||||
|
$pingedConnections->removeAllExcept($splClearer);
|
||||||
|
|
||||||
|
$lastPing = new Frame(uniqid(), true, Frame::OP_PING);
|
||||||
|
|
||||||
|
foreach ($this->connections as $key => $conn) {
|
||||||
|
$wsConn = $this->connections[$conn]->connection;
|
||||||
|
|
||||||
|
$wsConn->send($lastPing);
|
||||||
|
$pingedConnections->attach($wsConn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServerInterface.php
vendored
Normal file
14
src/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServerInterface.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket Server Interface
|
||||||
|
*/
|
||||||
|
interface WsServerInterface {
|
||||||
|
/**
|
||||||
|
* If any component in a stack supports a WebSocket sub-protocol return each supported in an array
|
||||||
|
* @return array
|
||||||
|
* @todo This method may be removed in future version (note that will not break code, just make some code obsolete)
|
||||||
|
*/
|
||||||
|
function getSubProtocols();
|
||||||
|
}
|
||||||
36
src/vendor/cboden/ratchet/tests/autobahn/bin/fuzzingserver.php
vendored
Normal file
36
src/vendor/cboden/ratchet/tests/autobahn/bin/fuzzingserver.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
class BinaryEcho implements \Ratchet\WebSocket\MessageComponentInterface {
|
||||||
|
public function onMessage(ConnectionInterface $from, \Ratchet\RFC6455\Messaging\MessageInterface $msg) {
|
||||||
|
$from->send($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$port = $argc > 1 ? $argv[1] : 8000;
|
||||||
|
$impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect');
|
||||||
|
|
||||||
|
$loop = new $impl;
|
||||||
|
$sock = new React\Socket\Server('0.0.0.0:' . $port, $loop);
|
||||||
|
|
||||||
|
$wsServer = new Ratchet\WebSocket\WsServer(new BinaryEcho);
|
||||||
|
// This is enabled to test https://github.com/ratchetphp/Ratchet/issues/430
|
||||||
|
// The time is left at 10 minutes so that it will not try to every ping anything
|
||||||
|
// This causes the Ratchet server to crash on test 2.7
|
||||||
|
$wsServer->enableKeepAlive($loop, 600);
|
||||||
|
|
||||||
|
$app = new Ratchet\Http\HttpServer($wsServer);
|
||||||
|
|
||||||
|
$server = new Ratchet\Server\IoServer($app, $sock, $loop);
|
||||||
|
$server->run();
|
||||||
15
src/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-all.json
vendored
Normal file
15
src/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-all.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"options": {"failByDrop": false}
|
||||||
|
, "outdir": "reports/ab"
|
||||||
|
|
||||||
|
, "servers": [
|
||||||
|
{"agent": "Ratchet/0.4 libevent", "url": "ws://localhost:8001", "options": {"version": 18}}
|
||||||
|
, {"agent": "Ratchet/0.4 libev", "url": "ws://localhost:8004", "options": {"version": 18}}
|
||||||
|
, {"agent": "Ratchet/0.4 streams", "url": "ws://localhost:8002", "options": {"version": 18}}
|
||||||
|
, {"agent": "AutobahnTestSuite/0.5.9", "url": "ws://localhost:8000", "options": {"version": 18}}
|
||||||
|
]
|
||||||
|
|
||||||
|
, "cases": ["*"]
|
||||||
|
, "exclude-cases": []
|
||||||
|
, "exclude-agent-cases": {}
|
||||||
|
}
|
||||||
12
src/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-profile.json
vendored
Normal file
12
src/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-profile.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"options": {"failByDrop": false}
|
||||||
|
, "outdir": "reports/profile"
|
||||||
|
|
||||||
|
, "servers": [
|
||||||
|
{"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}}
|
||||||
|
]
|
||||||
|
|
||||||
|
, "cases": ["9.7.4"]
|
||||||
|
, "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"]
|
||||||
|
, "exclude-agent-cases": {}
|
||||||
|
}
|
||||||
12
src/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-quick.json
vendored
Normal file
12
src/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-quick.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"options": {"failByDrop": false}
|
||||||
|
, "outdir": "reports/rfc"
|
||||||
|
|
||||||
|
, "servers": [
|
||||||
|
{"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}}
|
||||||
|
]
|
||||||
|
|
||||||
|
, "cases": ["*"]
|
||||||
|
, "exclude-cases": []
|
||||||
|
, "exclude-agent-cases": {}
|
||||||
|
}
|
||||||
4
src/vendor/cboden/ratchet/tests/bootstrap.php
vendored
Normal file
4
src/vendor/cboden/ratchet/tests/bootstrap.php
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$loader = require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
$loader->addPsr4('Ratchet\\', __DIR__ . '/helpers/Ratchet');
|
||||||
50
src/vendor/cboden/ratchet/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php
vendored
Normal file
50
src/vendor/cboden/ratchet/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
|
||||||
|
abstract class AbstractMessageComponentTestCase extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $_app;
|
||||||
|
protected $_serv;
|
||||||
|
protected $_conn;
|
||||||
|
|
||||||
|
abstract public function getConnectionClassString();
|
||||||
|
abstract public function getDecoratorClassString();
|
||||||
|
abstract public function getComponentClassString();
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_app = $this->getMock($this->getComponentClassString());
|
||||||
|
$decorator = $this->getDecoratorClassString();
|
||||||
|
$this->_serv = new $decorator($this->_app);
|
||||||
|
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
|
||||||
|
$this->doOpen($this->_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doOpen($conn) {
|
||||||
|
$this->_serv->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExpectedConnection() {
|
||||||
|
return new \PHPUnit_Framework_Constraint_IsInstanceOf($this->getConnectionClassString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpen() {
|
||||||
|
$this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection());
|
||||||
|
$this->doOpen($this->getMock('\Ratchet\ConnectionInterface'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnClose() {
|
||||||
|
$this->_app->expects($this->once())->method('onClose')->with($this->isExpectedConnection());
|
||||||
|
$this->_serv->onClose($this->_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnError() {
|
||||||
|
$e = new \Exception('Whoops!');
|
||||||
|
$this->_app->expects($this->once())->method('onError')->with($this->isExpectedConnection(), $e);
|
||||||
|
$this->_serv->onError($this->_conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function passthroughMessageTest($value) {
|
||||||
|
$this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $value);
|
||||||
|
$this->_serv->onMessage($this->_conn, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Component.php
vendored
Normal file
35
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Component.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Mock;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class Component implements MessageComponentInterface, WsServerInterface {
|
||||||
|
public $last = array();
|
||||||
|
|
||||||
|
public $protocols = array();
|
||||||
|
|
||||||
|
public function __construct(ComponentInterface $app = null) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubProtocols() {
|
||||||
|
return $this->protocols;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Connection.php
vendored
Normal file
20
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Connection.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Mock;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class Connection implements ConnectionInterface {
|
||||||
|
public $last = array(
|
||||||
|
'send' => ''
|
||||||
|
, 'close' => false
|
||||||
|
);
|
||||||
|
|
||||||
|
public $remoteAddress = '127.0.0.1';
|
||||||
|
|
||||||
|
public function send($data) {
|
||||||
|
$this->last[__FUNCTION__] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close() {
|
||||||
|
$this->last[__FUNCTION__] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/ConnectionDecorator.php
vendored
Normal file
22
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/ConnectionDecorator.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Mock;
|
||||||
|
use Ratchet\AbstractConnectionDecorator;
|
||||||
|
|
||||||
|
class ConnectionDecorator extends AbstractConnectionDecorator {
|
||||||
|
public $last = array(
|
||||||
|
'write' => ''
|
||||||
|
, 'end' => false
|
||||||
|
);
|
||||||
|
|
||||||
|
public function send($data) {
|
||||||
|
$this->last[__FUNCTION__] = $data;
|
||||||
|
|
||||||
|
$this->getConnection()->send($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close() {
|
||||||
|
$this->last[__FUNCTION__] = true;
|
||||||
|
|
||||||
|
$this->getConnection()->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/WampComponent.php
vendored
Normal file
43
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/WampComponent.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Mock;
|
||||||
|
use Ratchet\Wamp\WampServerInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class WampComponent implements WampServerInterface, WsServerInterface {
|
||||||
|
public $last = array();
|
||||||
|
|
||||||
|
public $protocols = array();
|
||||||
|
|
||||||
|
public function getSubProtocols() {
|
||||||
|
return $this->protocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onCall(ConnectionInterface $conn, $id, $procURI, array $params) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSubscribe(ConnectionInterface $conn, $topic) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onUnSubscribe(ConnectionInterface $conn, $topic) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/vendor/cboden/ratchet/tests/helpers/Ratchet/NullComponent.php
vendored
Normal file
28
src/vendor/cboden/ratchet/tests/helpers/Ratchet/NullComponent.php
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Ratchet\Wamp\WampServerInterface;
|
||||||
|
|
||||||
|
class NullComponent implements MessageComponentInterface, WsServerInterface, WampServerInterface {
|
||||||
|
public function onOpen(ConnectionInterface $conn) {}
|
||||||
|
|
||||||
|
public function onMessage(ConnectionInterface $conn, $msg) {}
|
||||||
|
|
||||||
|
public function onClose(ConnectionInterface $conn) {}
|
||||||
|
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {}
|
||||||
|
|
||||||
|
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {}
|
||||||
|
|
||||||
|
public function onSubscribe(ConnectionInterface $conn, $topic) {}
|
||||||
|
|
||||||
|
public function onUnSubscribe(ConnectionInterface $conn, $topic) {}
|
||||||
|
|
||||||
|
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude = array(), array $eligible = array()) {}
|
||||||
|
|
||||||
|
public function getSubProtocols() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Wamp/Stub/WsWampServerInterface.php
vendored
Normal file
7
src/vendor/cboden/ratchet/tests/helpers/Ratchet/Wamp/Stub/WsWampServerInterface.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp\Stub;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Ratchet\Wamp\WampServerInterface;
|
||||||
|
|
||||||
|
interface WsWampServerInterface extends WsServerInterface, WampServerInterface {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Stub;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
|
||||||
|
interface WsMessageComponentInterface extends MessageComponentInterface, WsServerInterface {
|
||||||
|
}
|
||||||
147
src/vendor/cboden/ratchet/tests/unit/AbstractConnectionDecoratorTest.php
vendored
Normal file
147
src/vendor/cboden/ratchet/tests/unit/AbstractConnectionDecoratorTest.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet;
|
||||||
|
use Ratchet\Mock\ConnectionDecorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\AbstractConnectionDecorator
|
||||||
|
* @covers Ratchet\ConnectionInterface
|
||||||
|
*/
|
||||||
|
class AbstractConnectionDecoratorTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $mock;
|
||||||
|
protected $l1;
|
||||||
|
protected $l2;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->mock = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$this->l1 = new ConnectionDecorator($this->mock);
|
||||||
|
$this->l2 = new ConnectionDecorator($this->l1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGet() {
|
||||||
|
$var = 'hello';
|
||||||
|
$val = 'world';
|
||||||
|
|
||||||
|
$this->mock->$var = $val;
|
||||||
|
|
||||||
|
$this->assertEquals($val, $this->l1->$var);
|
||||||
|
$this->assertEquals($val, $this->l2->$var);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSet() {
|
||||||
|
$var = 'Chris';
|
||||||
|
$val = 'Boden';
|
||||||
|
|
||||||
|
$this->l1->$var = $val;
|
||||||
|
|
||||||
|
$this->assertEquals($val, $this->mock->$var);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetLevel2() {
|
||||||
|
$var = 'Try';
|
||||||
|
$val = 'Again';
|
||||||
|
|
||||||
|
$this->l2->$var = $val;
|
||||||
|
|
||||||
|
$this->assertEquals($val, $this->mock->$var);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsSetTrue() {
|
||||||
|
$var = 'PHP';
|
||||||
|
$val = 'Ratchet';
|
||||||
|
|
||||||
|
$this->mock->$var = $val;
|
||||||
|
|
||||||
|
$this->assertTrue(isset($this->l1->$var));
|
||||||
|
$this->assertTrue(isset($this->l2->$var));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsSetFalse() {
|
||||||
|
$var = 'herp';
|
||||||
|
$val = 'derp';
|
||||||
|
|
||||||
|
$this->assertFalse(isset($this->l1->$var));
|
||||||
|
$this->assertFalse(isset($this->l2->$var));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnset() {
|
||||||
|
$var = 'Flying';
|
||||||
|
$val = 'Monkey';
|
||||||
|
|
||||||
|
$this->mock->$var = $val;
|
||||||
|
unset($this->l1->$var);
|
||||||
|
|
||||||
|
$this->assertFalse(isset($this->mock->$var));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnsetLevel2() {
|
||||||
|
$var = 'Flying';
|
||||||
|
$val = 'Monkey';
|
||||||
|
|
||||||
|
$this->mock->$var = $val;
|
||||||
|
unset($this->l2->$var);
|
||||||
|
|
||||||
|
$this->assertFalse(isset($this->mock->$var));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetConnection() {
|
||||||
|
$class = new \ReflectionClass('\\Ratchet\\AbstractConnectionDecorator');
|
||||||
|
$method = $class->getMethod('getConnection');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$conn = $method->invokeArgs($this->l1, array());
|
||||||
|
|
||||||
|
$this->assertSame($this->mock, $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetConnectionLevel2() {
|
||||||
|
$class = new \ReflectionClass('\\Ratchet\\AbstractConnectionDecorator');
|
||||||
|
$method = $class->getMethod('getConnection');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$conn = $method->invokeArgs($this->l2, array());
|
||||||
|
|
||||||
|
$this->assertSame($this->l1, $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWrapperCanStoreSelfInDecorator() {
|
||||||
|
$this->mock->decorator = $this->l1;
|
||||||
|
|
||||||
|
$this->assertSame($this->l1, $this->l2->decorator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecoratorRecursion() {
|
||||||
|
$this->mock->decorator = new \stdClass;
|
||||||
|
$this->mock->decorator->conn = $this->l1;
|
||||||
|
|
||||||
|
$this->assertSame($this->l1, $this->mock->decorator->conn);
|
||||||
|
$this->assertSame($this->l1, $this->l1->decorator->conn);
|
||||||
|
$this->assertSame($this->l1, $this->l2->decorator->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecoratorRecursionLevel2() {
|
||||||
|
$this->mock->decorator = new \stdClass;
|
||||||
|
$this->mock->decorator->conn = $this->l2;
|
||||||
|
|
||||||
|
$this->assertSame($this->l2, $this->mock->decorator->conn);
|
||||||
|
$this->assertSame($this->l2, $this->l1->decorator->conn);
|
||||||
|
$this->assertSame($this->l2, $this->l2->decorator->conn);
|
||||||
|
|
||||||
|
// just for fun
|
||||||
|
$this->assertSame($this->l2, $this->l2->decorator->conn->decorator->conn->decorator->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWarningGettingNothing() {
|
||||||
|
$this->setExpectedException('PHPUnit_Framework_Error');
|
||||||
|
$var = $this->mock->nonExistant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWarningGettingNothingLevel1() {
|
||||||
|
$this->setExpectedException('PHPUnit_Framework_Error');
|
||||||
|
$var = $this->l1->nonExistant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWarningGettingNothingLevel2() {
|
||||||
|
$this->setExpectedException('PHPUnit_Framework_Error');
|
||||||
|
$var = $this->l2->nonExistant;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/vendor/cboden/ratchet/tests/unit/Http/HttpRequestParserTest.php
vendored
Normal file
50
src/vendor/cboden/ratchet/tests/unit/Http/HttpRequestParserTest.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Http\HttpRequestParser
|
||||||
|
*/
|
||||||
|
class HttpRequestParserTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $parser;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->parser = new HttpRequestParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headersProvider() {
|
||||||
|
return array(
|
||||||
|
array(false, "GET / HTTP/1.1\r\nHost: socketo.me\r\n")
|
||||||
|
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n")
|
||||||
|
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n1")
|
||||||
|
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖")
|
||||||
|
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖\r\n\r\n")
|
||||||
|
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie\r\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider headersProvider
|
||||||
|
*/
|
||||||
|
public function testIsEom($expected, $message) {
|
||||||
|
$this->assertEquals($expected, $this->parser->isEom($message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBufferOverflowResponse() {
|
||||||
|
$conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
|
||||||
|
$this->parser->maxSize = 20;
|
||||||
|
|
||||||
|
$this->assertNull($this->parser->onMessage($conn, "GET / HTTP/1.1\r\n"));
|
||||||
|
|
||||||
|
$this->setExpectedException('OverflowException');
|
||||||
|
|
||||||
|
$this->parser->onMessage($conn, "Header-Is: Too Big");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReturnTypeIsRequest() {
|
||||||
|
$conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n");
|
||||||
|
|
||||||
|
$this->assertInstanceOf('\Psr\Http\Message\RequestInterface', $return);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/vendor/cboden/ratchet/tests/unit/Http/HttpServerTest.php
vendored
Normal file
64
src/vendor/cboden/ratchet/tests/unit/Http/HttpServerTest.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\AbstractMessageComponentTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Http\HttpServer
|
||||||
|
*/
|
||||||
|
class HttpServerTest extends AbstractMessageComponentTestCase {
|
||||||
|
public function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
$this->_conn->httpHeadersReceived = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnectionClassString() {
|
||||||
|
return '\Ratchet\ConnectionInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecoratorClassString() {
|
||||||
|
return '\Ratchet\Http\HttpServer';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentClassString() {
|
||||||
|
return '\Ratchet\Http\HttpServerInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpen() {
|
||||||
|
$headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n";
|
||||||
|
|
||||||
|
$this->_conn->httpHeadersReceived = false;
|
||||||
|
$this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection());
|
||||||
|
$this->_serv->onMessage($this->_conn, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnMessageAfterHeaders() {
|
||||||
|
$headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n";
|
||||||
|
$this->_conn->httpHeadersReceived = false;
|
||||||
|
$this->_serv->onMessage($this->_conn, $headers);
|
||||||
|
|
||||||
|
$message = "Hello World!";
|
||||||
|
$this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message);
|
||||||
|
$this->_serv->onMessage($this->_conn, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBufferOverflow() {
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
$this->_conn->httpHeadersReceived = false;
|
||||||
|
|
||||||
|
$this->_serv->onMessage($this->_conn, str_repeat('a', 5000));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCloseIfNotEstablished() {
|
||||||
|
$this->_conn->httpHeadersReceived = false;
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
$this->_serv->onError($this->_conn, new \Exception('Whoops!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBufferHeaders() {
|
||||||
|
$this->_conn->httpHeadersReceived = false;
|
||||||
|
$this->_app->expects($this->never())->method('onOpen');
|
||||||
|
$this->_app->expects($this->never())->method('onMessage');
|
||||||
|
|
||||||
|
$this->_serv->onMessage($this->_conn, "GET / HTTP/1.1");
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/vendor/cboden/ratchet/tests/unit/Http/OriginCheckTest.php
vendored
Normal file
46
src/vendor/cboden/ratchet/tests/unit/Http/OriginCheckTest.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\AbstractMessageComponentTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Http\OriginCheck
|
||||||
|
*/
|
||||||
|
class OriginCheckTest extends AbstractMessageComponentTestCase {
|
||||||
|
protected $_reqStub;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_reqStub = $this->getMock('Psr\Http\Message\RequestInterface');
|
||||||
|
$this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue(['localhost']));
|
||||||
|
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->_serv->allowedOrigins[] = 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doOpen($conn) {
|
||||||
|
$this->_serv->onOpen($conn, $this->_reqStub);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnectionClassString() {
|
||||||
|
return '\Ratchet\ConnectionInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecoratorClassString() {
|
||||||
|
return '\Ratchet\Http\OriginCheck';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentClassString() {
|
||||||
|
return '\Ratchet\Http\HttpServerInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCloseOnNonMatchingOrigin() {
|
||||||
|
$this->_serv->allowedOrigins = ['socketo.me'];
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
|
||||||
|
$this->_serv->onOpen($this->_conn, $this->_reqStub);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnMessage() {
|
||||||
|
$this->passthroughMessageTest('Hello World!');
|
||||||
|
}
|
||||||
|
}
|
||||||
165
src/vendor/cboden/ratchet/tests/unit/Http/RouterTest.php
vendored
Normal file
165
src/vendor/cboden/ratchet/tests/unit/Http/RouterTest.php
vendored
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
|
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||||
|
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
|
||||||
|
use Symfony\Component\Routing\RequestContext;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Http\Router
|
||||||
|
*/
|
||||||
|
class RouterTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $_router;
|
||||||
|
protected $_matcher;
|
||||||
|
protected $_conn;
|
||||||
|
protected $_uri;
|
||||||
|
protected $_req;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$this->_uri = $this->getMock('Psr\Http\Message\UriInterface');
|
||||||
|
$this->_req = $this->getMock('\Psr\Http\Message\RequestInterface');
|
||||||
|
$this->_req
|
||||||
|
->expects($this->any())
|
||||||
|
->method('getUri')
|
||||||
|
->will($this->returnValue($this->_uri));
|
||||||
|
$this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
|
||||||
|
$this->_matcher
|
||||||
|
->expects($this->any())
|
||||||
|
->method('getContext')
|
||||||
|
->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext')));
|
||||||
|
$this->_router = new Router($this->_matcher);
|
||||||
|
|
||||||
|
$this->_uri->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/'));
|
||||||
|
$this->_uri->expects($this->any())->method('withQuery')->with($this->callback(function($val) {
|
||||||
|
$this->setResult($val);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}))->will($this->returnSelf());
|
||||||
|
$this->_uri->expects($this->any())->method('getQuery')->will($this->returnCallback([$this, 'getResult']));
|
||||||
|
$this->_req->expects($this->any())->method('withUri')->will($this->returnSelf());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFourOhFour() {
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
|
||||||
|
$nope = new ResourceNotFoundException;
|
||||||
|
$this->_matcher->expects($this->any())->method('match')->will($this->throwException($nope));
|
||||||
|
|
||||||
|
$this->_router->onOpen($this->_conn, $this->_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullRequest() {
|
||||||
|
$this->setExpectedException('\UnexpectedValueException');
|
||||||
|
$this->_router->onOpen($this->_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testControllerIsMessageComponentInterface() {
|
||||||
|
$this->setExpectedException('\UnexpectedValueException');
|
||||||
|
$this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => new \StdClass)));
|
||||||
|
$this->_router->onOpen($this->_conn, $this->_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testControllerOnOpen() {
|
||||||
|
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
|
||||||
|
$this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller)));
|
||||||
|
$this->_router->onOpen($this->_conn, $this->_req);
|
||||||
|
|
||||||
|
$expectedConn = new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\ConnectionInterface');
|
||||||
|
$controller->expects($this->once())->method('onOpen')->with($expectedConn, $this->_req);
|
||||||
|
|
||||||
|
$this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller)));
|
||||||
|
$this->_router->onOpen($this->_conn, $this->_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testControllerOnMessageBubbles() {
|
||||||
|
$message = "The greatest trick the Devil ever pulled was convincing the world he didn't exist";
|
||||||
|
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
|
||||||
|
$controller->expects($this->once())->method('onMessage')->with($this->_conn, $message);
|
||||||
|
|
||||||
|
$this->_conn->controller = $controller;
|
||||||
|
|
||||||
|
$this->_router->onMessage($this->_conn, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testControllerOnCloseBubbles() {
|
||||||
|
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
|
||||||
|
$controller->expects($this->once())->method('onClose')->with($this->_conn);
|
||||||
|
|
||||||
|
$this->_conn->controller = $controller;
|
||||||
|
|
||||||
|
$this->_router->onClose($this->_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testControllerOnErrorBubbles() {
|
||||||
|
$e= new \Exception('One cannot be betrayed if one has no exceptions');
|
||||||
|
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
|
||||||
|
$controller->expects($this->once())->method('onError')->with($this->_conn, $e);
|
||||||
|
|
||||||
|
$this->_conn->controller = $controller;
|
||||||
|
|
||||||
|
$this->_router->onError($this->_conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRouterGeneratesRouteParameters() {
|
||||||
|
/** @var $controller WsServerInterface */
|
||||||
|
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
|
||||||
|
/** @var $matcher UrlMatcherInterface */
|
||||||
|
$this->_matcher->expects($this->any())->method('match')->will(
|
||||||
|
$this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux'])
|
||||||
|
);
|
||||||
|
$conn = $this->getMock('Ratchet\Mock\Connection');
|
||||||
|
|
||||||
|
$router = new Router($this->_matcher);
|
||||||
|
|
||||||
|
$router->onOpen($conn, $this->_req);
|
||||||
|
|
||||||
|
$this->assertEquals('foo=bar&baz=qux', $this->_req->getUri()->getQuery());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryParams() {
|
||||||
|
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
|
||||||
|
$this->_matcher->expects($this->any())->method('match')->will(
|
||||||
|
$this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux'])
|
||||||
|
);
|
||||||
|
|
||||||
|
$conn = $this->getMock('Ratchet\Mock\Connection');
|
||||||
|
$request = $this->getMock('Psr\Http\Message\RequestInterface');
|
||||||
|
$uri = new \GuzzleHttp\Psr7\Uri('ws://doesnt.matter/endpoint?hello=world&foo=nope');
|
||||||
|
|
||||||
|
$request->expects($this->any())->method('getUri')->will($this->returnCallback(function() use (&$uri) {
|
||||||
|
return $uri;
|
||||||
|
}));
|
||||||
|
$request->expects($this->any())->method('withUri')->with($this->callback(function($url) use (&$uri) {
|
||||||
|
$uri = $url;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}))->will($this->returnSelf());
|
||||||
|
|
||||||
|
$router = new Router($this->_matcher);
|
||||||
|
$router->onOpen($conn, $request);
|
||||||
|
|
||||||
|
$this->assertEquals('foo=nope&baz=qux&hello=world', $request->getUri()->getQuery());
|
||||||
|
$this->assertEquals('ws', $request->getUri()->getScheme());
|
||||||
|
$this->assertEquals('doesnt.matter', $request->getUri()->getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImpatientClientOverflow() {
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
|
||||||
|
$header = "GET /nope HTTP/1.1
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: upgrade
|
||||||
|
Host: localhost
|
||||||
|
Origin: http://localhost
|
||||||
|
Sec-WebSocket-Version: 13\r\n\r\n";
|
||||||
|
|
||||||
|
$app = new HttpServer(new Router(new UrlMatcher(new RouteCollection, new RequestContext)));
|
||||||
|
$app->onOpen($this->_conn);
|
||||||
|
$app->onMessage($this->_conn, $header);
|
||||||
|
$app->onMessage($this->_conn, 'Silly body');
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/vendor/cboden/ratchet/tests/unit/Server/EchoServerTest.php
vendored
Normal file
26
src/vendor/cboden/ratchet/tests/unit/Server/EchoServerTest.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\Server\EchoServer;
|
||||||
|
|
||||||
|
class EchoServerTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $_conn;
|
||||||
|
protected $_comp;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$this->_comp = new EchoServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMessageEchod() {
|
||||||
|
$message = 'Tillsonburg, my back still aches when I hear that word.';
|
||||||
|
$this->_conn->expects($this->once())->method('send')->with($message);
|
||||||
|
$this->_comp->onMessage($this->_conn, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testErrorClosesConnection() {
|
||||||
|
ob_start();
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
$this->_comp->onError($this->_conn, new \Exception);
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/vendor/cboden/ratchet/tests/unit/Server/FlashPolicyComponentTest.php
vendored
Normal file
152
src/vendor/cboden/ratchet/tests/unit/Server/FlashPolicyComponentTest.php
vendored
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Application\Server;
|
||||||
|
use Ratchet\Server\FlashPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Server\FlashPolicy
|
||||||
|
*/
|
||||||
|
class FlashPolicyTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
|
||||||
|
protected $_policy;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_policy = new FlashPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPolicyRender() {
|
||||||
|
$this->_policy->setSiteControl('all');
|
||||||
|
$this->_policy->addAllowedAccess('example.com', '*');
|
||||||
|
$this->_policy->addAllowedAccess('dev.example.com', '*');
|
||||||
|
|
||||||
|
$this->assertInstanceOf('SimpleXMLElement', $this->_policy->renderPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidPolicyReader() {
|
||||||
|
$this->setExpectedException('UnexpectedValueException');
|
||||||
|
$this->_policy->renderPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidDomainPolicyReader() {
|
||||||
|
$this->setExpectedException('UnexpectedValueException');
|
||||||
|
$this->_policy->setSiteControl('all');
|
||||||
|
$this->_policy->addAllowedAccess('dev.example.*', '*');
|
||||||
|
$this->_policy->renderPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider siteControl
|
||||||
|
*/
|
||||||
|
public function testSiteControlValidation($accept, $permittedCrossDomainPolicies) {
|
||||||
|
$this->assertEquals($accept, $this->_policy->validateSiteControl($permittedCrossDomainPolicies));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function siteControl() {
|
||||||
|
return array(
|
||||||
|
array(true, 'all')
|
||||||
|
, array(true, 'none')
|
||||||
|
, array(true, 'master-only')
|
||||||
|
, array(false, 'by-content-type')
|
||||||
|
, array(false, 'by-ftp-filename')
|
||||||
|
, array(false, '')
|
||||||
|
, array(false, 'all ')
|
||||||
|
, array(false, 'asdf')
|
||||||
|
, array(false, '@893830')
|
||||||
|
, array(false, '*')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider URI
|
||||||
|
*/
|
||||||
|
public function testDomainValidation($accept, $domain) {
|
||||||
|
$this->assertEquals($accept, $this->_policy->validateDomain($domain));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function URI() {
|
||||||
|
return array(
|
||||||
|
array(true, '*')
|
||||||
|
, array(true, 'example.com')
|
||||||
|
, array(true, 'exam-ple.com')
|
||||||
|
, array(true, '*.example.com')
|
||||||
|
, array(true, 'www.example.com')
|
||||||
|
, array(true, 'dev.dev.example.com')
|
||||||
|
, array(true, 'http://example.com')
|
||||||
|
, array(true, 'https://example.com')
|
||||||
|
, array(true, 'http://*.example.com')
|
||||||
|
, array(false, 'exam*ple.com')
|
||||||
|
, array(true, '127.0.255.1')
|
||||||
|
, array(true, 'localhost')
|
||||||
|
, array(false, 'www.example.*')
|
||||||
|
, array(false, 'www.exa*le.com')
|
||||||
|
, array(false, 'www.example.*com')
|
||||||
|
, array(false, '*.example.*')
|
||||||
|
, array(false, 'gasldf*$#a0sdf0a8sdf')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ports
|
||||||
|
*/
|
||||||
|
public function testPortValidation($accept, $ports) {
|
||||||
|
$this->assertEquals($accept, $this->_policy->validatePorts($ports));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function ports() {
|
||||||
|
return array(
|
||||||
|
array(true, '*')
|
||||||
|
, array(true, '80')
|
||||||
|
, array(true, '80,443')
|
||||||
|
, array(true, '507,516-523')
|
||||||
|
, array(true, '507,516-523,333')
|
||||||
|
, array(true, '507,516-523,507,516-523')
|
||||||
|
, array(false, '516-')
|
||||||
|
, array(true, '516-523,11')
|
||||||
|
, array(false, '516,-523,11')
|
||||||
|
, array(false, 'example')
|
||||||
|
, array(false, 'asdf,123')
|
||||||
|
, array(false, '--')
|
||||||
|
, array(false, ',,,')
|
||||||
|
, array(false, '838*')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddAllowedAccessOnlyAcceptsValidPorts() {
|
||||||
|
$this->setExpectedException('UnexpectedValueException');
|
||||||
|
|
||||||
|
$this->_policy->addAllowedAccess('*', 'nope');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetSiteControlThrowsException() {
|
||||||
|
$this->setExpectedException('UnexpectedValueException');
|
||||||
|
|
||||||
|
$this->_policy->setSiteControl('nope');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testErrorClosesConnection() {
|
||||||
|
$conn = $this->getMock('\\Ratchet\\ConnectionInterface');
|
||||||
|
$conn->expects($this->once())->method('close');
|
||||||
|
|
||||||
|
$this->_policy->onError($conn, new \Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnMessageSendsString() {
|
||||||
|
$this->_policy->addAllowedAccess('*', '*');
|
||||||
|
|
||||||
|
$conn = $this->getMock('\\Ratchet\\ConnectionInterface');
|
||||||
|
$conn->expects($this->once())->method('send')->with($this->isType('string'));
|
||||||
|
|
||||||
|
$this->_policy->onMessage($conn, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnOpenExists() {
|
||||||
|
$this->assertTrue(method_exists($this->_policy, 'onOpen'));
|
||||||
|
$conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$this->_policy->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnCloseExists() {
|
||||||
|
$this->assertTrue(method_exists($this->_policy, 'onClose'));
|
||||||
|
$conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$this->_policy->onClose($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/vendor/cboden/ratchet/tests/unit/Server/IoConnectionTest.php
vendored
Normal file
32
src/vendor/cboden/ratchet/tests/unit/Server/IoConnectionTest.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Application\Server;
|
||||||
|
use Ratchet\Server\IoConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Server\IoConnection
|
||||||
|
*/
|
||||||
|
class IoConnectionTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $sock;
|
||||||
|
protected $conn;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->sock = $this->getMock('\\React\\Socket\\ConnectionInterface');
|
||||||
|
$this->conn = new IoConnection($this->sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCloseBubbles() {
|
||||||
|
$this->sock->expects($this->once())->method('end');
|
||||||
|
$this->conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSendBubbles() {
|
||||||
|
$msg = '6 hour rides are productive';
|
||||||
|
|
||||||
|
$this->sock->expects($this->once())->method('write')->with($msg);
|
||||||
|
$this->conn->send($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSendReturnsSelf() {
|
||||||
|
$this->assertSame($this->conn, $this->conn->send('fluent interface'));
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/vendor/cboden/ratchet/tests/unit/Server/IoServerTest.php
vendored
Normal file
127
src/vendor/cboden/ratchet/tests/unit/Server/IoServerTest.php
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\Server\IoServer;
|
||||||
|
use React\EventLoop\StreamSelectLoop;
|
||||||
|
use React\EventLoop\LoopInterface;
|
||||||
|
use React\Socket\Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Server\IoServer
|
||||||
|
*/
|
||||||
|
class IoServerTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $server;
|
||||||
|
|
||||||
|
protected $app;
|
||||||
|
|
||||||
|
protected $port;
|
||||||
|
|
||||||
|
protected $reactor;
|
||||||
|
|
||||||
|
protected function tickLoop(LoopInterface $loop) {
|
||||||
|
$loop->futureTick(function () use ($loop) {
|
||||||
|
$loop->stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
$loop->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->app = $this->getMock('\\Ratchet\\MessageComponentInterface');
|
||||||
|
|
||||||
|
$loop = new StreamSelectLoop;
|
||||||
|
$this->reactor = new Server(0, $loop);
|
||||||
|
|
||||||
|
$uri = $this->reactor->getAddress();
|
||||||
|
$this->port = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_PORT);
|
||||||
|
$this->server = new IoServer($this->app, $this->reactor, $loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnOpen() {
|
||||||
|
$this->app->expects($this->once())->method('onOpen')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface'));
|
||||||
|
|
||||||
|
$client = stream_socket_client("tcp://localhost:{$this->port}");
|
||||||
|
|
||||||
|
$this->tickLoop($this->server->loop);
|
||||||
|
|
||||||
|
//$this->assertTrue(is_string($this->app->last['onOpen'][0]->remoteAddress));
|
||||||
|
//$this->assertTrue(is_int($this->app->last['onOpen'][0]->resourceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnData() {
|
||||||
|
$msg = 'Hello World!';
|
||||||
|
|
||||||
|
$this->app->expects($this->once())->method('onMessage')->with(
|
||||||
|
$this->isInstanceOf('\\Ratchet\\ConnectionInterface')
|
||||||
|
, $msg
|
||||||
|
);
|
||||||
|
|
||||||
|
$client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1);
|
||||||
|
socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096);
|
||||||
|
socket_set_block($client);
|
||||||
|
socket_connect($client, 'localhost', $this->port);
|
||||||
|
|
||||||
|
$this->tickLoop($this->server->loop);
|
||||||
|
|
||||||
|
socket_write($client, $msg);
|
||||||
|
$this->tickLoop($this->server->loop);
|
||||||
|
|
||||||
|
socket_shutdown($client, 1);
|
||||||
|
socket_shutdown($client, 0);
|
||||||
|
socket_close($client);
|
||||||
|
|
||||||
|
$this->tickLoop($this->server->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnClose() {
|
||||||
|
$this->app->expects($this->once())->method('onClose')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface'));
|
||||||
|
|
||||||
|
$client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1);
|
||||||
|
socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096);
|
||||||
|
socket_set_block($client);
|
||||||
|
socket_connect($client, 'localhost', $this->port);
|
||||||
|
|
||||||
|
$this->tickLoop($this->server->loop);
|
||||||
|
|
||||||
|
socket_shutdown($client, 1);
|
||||||
|
socket_shutdown($client, 0);
|
||||||
|
socket_close($client);
|
||||||
|
|
||||||
|
$this->tickLoop($this->server->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFactory() {
|
||||||
|
$this->assertInstanceOf('\\Ratchet\\Server\\IoServer', IoServer::factory($this->app, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoLoopProvidedError() {
|
||||||
|
$this->setExpectedException('RuntimeException');
|
||||||
|
|
||||||
|
$io = new IoServer($this->app, $this->reactor);
|
||||||
|
$io->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnErrorPassesException() {
|
||||||
|
$conn = $this->getMock('\\React\\Socket\\ConnectionInterface');
|
||||||
|
$conn->decor = $this->getMock('\\Ratchet\\ConnectionInterface');
|
||||||
|
$err = new \Exception("Nope");
|
||||||
|
|
||||||
|
$this->app->expects($this->once())->method('onError')->with($conn->decor, $err);
|
||||||
|
|
||||||
|
$this->server->handleError($err, $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onErrorCalledWhenExceptionThrown() {
|
||||||
|
$this->markTestIncomplete("Need to learn how to throw an exception from a mock");
|
||||||
|
|
||||||
|
$conn = $this->getMock('\\React\\Socket\\ConnectionInterface');
|
||||||
|
$this->server->handleConnect($conn);
|
||||||
|
|
||||||
|
$e = new \Exception;
|
||||||
|
$this->app->expects($this->once())->method('onMessage')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface'), 'f')->will($e);
|
||||||
|
$this->app->expects($this->once())->method('onError')->with($this->instanceOf('\\Ratchet\\ConnectionInterface', $e));
|
||||||
|
|
||||||
|
$this->server->handleData('f', $conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/vendor/cboden/ratchet/tests/unit/Server/IpBlackListComponentTest.php
vendored
Normal file
125
src/vendor/cboden/ratchet/tests/unit/Server/IpBlackListComponentTest.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Server;
|
||||||
|
use Ratchet\Server\IpBlackList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Server\IpBlackList
|
||||||
|
*/
|
||||||
|
class IpBlackListTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $blocker;
|
||||||
|
protected $mock;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->mock = $this->getMock('\\Ratchet\\MessageComponentInterface');
|
||||||
|
$this->blocker = new IpBlackList($this->mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnOpen() {
|
||||||
|
$this->mock->expects($this->exactly(3))->method('onOpen');
|
||||||
|
|
||||||
|
$conn1 = $this->newConn();
|
||||||
|
$conn2 = $this->newConn();
|
||||||
|
$conn3 = $this->newConn();
|
||||||
|
|
||||||
|
$this->blocker->onOpen($conn1);
|
||||||
|
$this->blocker->onOpen($conn3);
|
||||||
|
$this->blocker->onOpen($conn2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBlockDoesNotTriggerOnOpen() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->blocker->blockAddress($conn->remoteAddress);
|
||||||
|
|
||||||
|
$this->mock->expects($this->never())->method('onOpen');
|
||||||
|
|
||||||
|
$ret = $this->blocker->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBlockDoesNotTriggerOnClose() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->blocker->blockAddress($conn->remoteAddress);
|
||||||
|
|
||||||
|
$this->mock->expects($this->never())->method('onClose');
|
||||||
|
|
||||||
|
$ret = $this->blocker->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnMessageDecoration() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$msg = 'Hello not being blocked';
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('onMessage')->with($conn, $msg);
|
||||||
|
|
||||||
|
$this->blocker->onMessage($conn, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnCloseDecoration() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('onClose')->with($conn);
|
||||||
|
|
||||||
|
$this->blocker->onClose($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBlockClosesConnection() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$this->blocker->blockAddress($conn->remoteAddress);
|
||||||
|
|
||||||
|
$conn->expects($this->once())->method('close');
|
||||||
|
|
||||||
|
$this->blocker->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddAndRemoveWithFluentInterfaces() {
|
||||||
|
$blockOne = '127.0.0.1';
|
||||||
|
$blockTwo = '192.168.1.1';
|
||||||
|
$unblock = '75.119.207.140';
|
||||||
|
|
||||||
|
$this->blocker
|
||||||
|
->blockAddress($unblock)
|
||||||
|
->blockAddress($blockOne)
|
||||||
|
->unblockAddress($unblock)
|
||||||
|
->blockAddress($blockTwo)
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->assertEquals(array($blockOne, $blockTwo), $this->blocker->getBlockedAddresses());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecoratorPassesErrors() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$e = new \Exception('I threw an error');
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('onError')->with($conn, $e);
|
||||||
|
|
||||||
|
$this->blocker->onError($conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addressProvider() {
|
||||||
|
return array(
|
||||||
|
array('127.0.0.1', '127.0.0.1')
|
||||||
|
, array('localhost', 'localhost')
|
||||||
|
, array('fe80::1%lo0', 'fe80::1%lo0')
|
||||||
|
, array('127.0.0.1', '127.0.0.1:6392')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider addressProvider
|
||||||
|
*/
|
||||||
|
public function testFilterAddress($expected, $input) {
|
||||||
|
$this->assertEquals($expected, $this->blocker->filterAddress($input));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnblockingSilentlyFails() {
|
||||||
|
$this->assertInstanceOf('\\Ratchet\\Server\\IpBlackList', $this->blocker->unblockAddress('localhost'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newConn() {
|
||||||
|
$conn = $this->getMock('\\Ratchet\\ConnectionInterface');
|
||||||
|
$conn->remoteAddress = '127.0.0.1';
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vendor/cboden/ratchet/tests/unit/Session/Serialize/PhpHandlerTest.php
vendored
Normal file
43
src/vendor/cboden/ratchet/tests/unit/Session/Serialize/PhpHandlerTest.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Serialize;
|
||||||
|
use Ratchet\Session\Serialize\PhpHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Session\Serialize\PhpHandler
|
||||||
|
*/
|
||||||
|
class PhpHandlerTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $_handler;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_handler = new PhpHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serializedProvider() {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}'
|
||||||
|
, array(
|
||||||
|
'_sf2_attributes' => array(
|
||||||
|
'hello' => 'world'
|
||||||
|
, 'last' => 1332872102
|
||||||
|
)
|
||||||
|
, '_sf2_flashes' => array()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider serializedProvider
|
||||||
|
*/
|
||||||
|
public function testUnserialize($in, $expected) {
|
||||||
|
$this->assertEquals($expected, $this->_handler->unserialize($in));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider serializedProvider
|
||||||
|
*/
|
||||||
|
public function testSerialize($serialized, $original) {
|
||||||
|
$this->assertEquals($serialized, $this->_handler->serialize($original));
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/vendor/cboden/ratchet/tests/unit/Session/SessionComponentTest.php
vendored
Normal file
126
src/vendor/cboden/ratchet/tests/unit/Session/SessionComponentTest.php
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session;
|
||||||
|
use Ratchet\AbstractMessageComponentTestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Session\SessionProvider
|
||||||
|
* @covers Ratchet\Session\Storage\VirtualSessionStorage
|
||||||
|
* @covers Ratchet\Session\Storage\Proxy\VirtualProxy
|
||||||
|
*/
|
||||||
|
class SessionProviderTest extends AbstractMessageComponentTestCase {
|
||||||
|
public function setUp() {
|
||||||
|
return $this->markTestIncomplete('Test needs to be updated for ini_set issue in PHP 7.2');
|
||||||
|
|
||||||
|
if (!class_exists('Symfony\Component\HttpFoundation\Session\Session')) {
|
||||||
|
return $this->markTestSkipped('Dependency of Symfony HttpFoundation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::setUp();
|
||||||
|
$this->_serv = new SessionProvider($this->_app, new NullSessionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
ini_set('session.serialize_handler', 'php');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnectionClassString() {
|
||||||
|
return '\Ratchet\ConnectionInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecoratorClassString() {
|
||||||
|
return '\Ratchet\NullComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentClassString() {
|
||||||
|
return '\Ratchet\Http\HttpServerInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function classCaseProvider() {
|
||||||
|
return array(
|
||||||
|
array('php', 'Php')
|
||||||
|
, array('php_binary', 'PhpBinary')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider classCaseProvider
|
||||||
|
*/
|
||||||
|
public function testToClassCase($in, $out) {
|
||||||
|
$ref = new \ReflectionClass('\\Ratchet\\Session\\SessionProvider');
|
||||||
|
$method = $ref->getMethod('toClassCase');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$component = new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface'));
|
||||||
|
$this->assertEquals($out, $method->invokeArgs($component, array($in)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I think I have severely butchered this test...it's not so much of a unit test as it is a full-fledged component test
|
||||||
|
*/
|
||||||
|
public function testConnectionValueFromPdo() {
|
||||||
|
if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
|
||||||
|
return $this->markTestSkipped('Session test requires PDO and pdo_sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessionId = md5('testSession');
|
||||||
|
|
||||||
|
$dbOptions = array(
|
||||||
|
'db_table' => 'sessions'
|
||||||
|
, 'db_id_col' => 'sess_id'
|
||||||
|
, 'db_data_col' => 'sess_data'
|
||||||
|
, 'db_time_col' => 'sess_time'
|
||||||
|
, 'db_lifetime_col' => 'sess_lifetime'
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo = new \PDO("sqlite::memory:");
|
||||||
|
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||||
|
$pdo->exec(vsprintf("CREATE TABLE %s (%s TEXT NOT NULL PRIMARY KEY, %s BLOB NOT NULL, %s INTEGER NOT NULL, %s INTEGER)", $dbOptions));
|
||||||
|
|
||||||
|
$pdoHandler = new PdoSessionHandler($pdo, $dbOptions);
|
||||||
|
$pdoHandler->write($sessionId, '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}');
|
||||||
|
|
||||||
|
$component = new SessionProvider($this->getMock($this->getComponentClassString()), $pdoHandler, array('auto_start' => 1));
|
||||||
|
$connection = $this->getMock('Ratchet\\ConnectionInterface');
|
||||||
|
|
||||||
|
$headers = $this->getMock('Psr\Http\Message\RequestInterface');
|
||||||
|
$headers->expects($this->once())->method('getHeader')->will($this->returnValue([ini_get('session.name') . "={$sessionId};"]));
|
||||||
|
|
||||||
|
$component->onOpen($connection, $headers);
|
||||||
|
|
||||||
|
$this->assertEquals('world', $connection->Session->get('hello'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newConn() {
|
||||||
|
$conn = $this->getMock('Ratchet\ConnectionInterface');
|
||||||
|
|
||||||
|
$headers = $this->getMock('Psr\Http\Message\Request', array('getCookie'), array('POST', '/', array()));
|
||||||
|
$headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null));
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnMessageDecorator() {
|
||||||
|
$message = "Database calls are usually blocking :(";
|
||||||
|
$this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message);
|
||||||
|
$this->_serv->onMessage($this->_conn, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRejectInvalidSeralizers() {
|
||||||
|
if (!function_exists('wddx_serialize_value')) {
|
||||||
|
$this->markTestSkipped();
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('session.serialize_handler', 'wddx');
|
||||||
|
$this->setExpectedException('\RuntimeException');
|
||||||
|
new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doOpen($conn) {
|
||||||
|
$request = $this->getMock('Psr\Http\Message\RequestInterface');
|
||||||
|
$request->expects($this->any())->method('getHeader')->will($this->returnValue([]));
|
||||||
|
|
||||||
|
$this->_serv->onOpen($conn, $request);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/vendor/cboden/ratchet/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php
vendored
Normal file
53
src/vendor/cboden/ratchet/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Session\Storage;
|
||||||
|
use Ratchet\Session\Serialize\PhpHandler;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
|
||||||
|
|
||||||
|
class VirtualSessionStoragePDOTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
/**
|
||||||
|
* @var VirtualSessionStorage
|
||||||
|
*/
|
||||||
|
protected $_virtualSessionStorage;
|
||||||
|
|
||||||
|
protected $_pathToDB;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
|
||||||
|
return $this->markTestSkipped('Session test requires PDO and pdo_sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema = <<<SQL
|
||||||
|
CREATE TABLE `sessions` (
|
||||||
|
`sess_id` VARBINARY(128) NOT NULL PRIMARY KEY,
|
||||||
|
`sess_data` BLOB NOT NULL,
|
||||||
|
`sess_time` INTEGER UNSIGNED NOT NULL,
|
||||||
|
`sess_lifetime` MEDIUMINT NOT NULL
|
||||||
|
);
|
||||||
|
SQL;
|
||||||
|
$this->_pathToDB = tempnam(sys_get_temp_dir(), 'SQ3');;
|
||||||
|
$dsn = 'sqlite:' . $this->_pathToDB;
|
||||||
|
|
||||||
|
$pdo = new \PDO($dsn);
|
||||||
|
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||||
|
$pdo->exec($schema);
|
||||||
|
$pdo = null;
|
||||||
|
|
||||||
|
$sessionHandler = new PdoSessionHandler($dsn);
|
||||||
|
$serializer = new PhpHandler();
|
||||||
|
$this->_virtualSessionStorage = new VirtualSessionStorage($sessionHandler, 'foobar', $serializer);
|
||||||
|
$this->_virtualSessionStorage->registerBag(new FlashBag());
|
||||||
|
$this->_virtualSessionStorage->registerBag(new AttributeBag());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
unlink($this->_pathToDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStartWithDSN() {
|
||||||
|
$this->_virtualSessionStorage->start();
|
||||||
|
|
||||||
|
$this->assertTrue($this->_virtualSessionStorage->isStarted());
|
||||||
|
}
|
||||||
|
}
|
||||||
295
src/vendor/cboden/ratchet/tests/unit/Wamp/ServerProtocolTest.php
vendored
Normal file
295
src/vendor/cboden/ratchet/tests/unit/Wamp/ServerProtocolTest.php
vendored
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\Mock\Connection;
|
||||||
|
use Ratchet\Mock\WampComponent as TestComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Ratchet\Wamp\ServerProtocol
|
||||||
|
* @covers \Ratchet\Wamp\WampServerInterface
|
||||||
|
* @covers \Ratchet\Wamp\WampConnection
|
||||||
|
*/
|
||||||
|
class ServerProtocolTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $_comp;
|
||||||
|
|
||||||
|
protected $_app;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->_app = new TestComponent;
|
||||||
|
$this->_comp = new ServerProtocol($this->_app);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newConn() {
|
||||||
|
return new Connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invalidMessageProvider() {
|
||||||
|
return [
|
||||||
|
[0]
|
||||||
|
, [3]
|
||||||
|
, [4]
|
||||||
|
, [8]
|
||||||
|
, [9]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider invalidMessageProvider
|
||||||
|
*/
|
||||||
|
public function testInvalidMessages($type) {
|
||||||
|
$this->setExpectedException('\Ratchet\Wamp\Exception');
|
||||||
|
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode([$type]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWelcomeMessage() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
|
||||||
|
$message = $conn->last['send'];
|
||||||
|
$json = json_decode($message);
|
||||||
|
|
||||||
|
$this->assertEquals(4, count($json));
|
||||||
|
$this->assertEquals(0, $json[0]);
|
||||||
|
$this->assertTrue(is_string($json[1]));
|
||||||
|
$this->assertEquals(1, $json[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscribe() {
|
||||||
|
$uri = 'http://example.com';
|
||||||
|
$clientMessage = array(5, $uri);
|
||||||
|
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode($clientMessage));
|
||||||
|
|
||||||
|
$this->assertEquals($uri, $this->_app->last['onSubscribe'][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnSubscribe() {
|
||||||
|
$uri = 'http://example.com/endpoint';
|
||||||
|
$clientMessage = array(6, $uri);
|
||||||
|
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode($clientMessage));
|
||||||
|
|
||||||
|
$this->assertEquals($uri, $this->_app->last['onUnSubscribe'][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callProvider() {
|
||||||
|
return [
|
||||||
|
[2, 'a', 'b']
|
||||||
|
, [2, ['a', 'b']]
|
||||||
|
, [1, 'one']
|
||||||
|
, [3, 'one', 'two', 'three']
|
||||||
|
, [3, ['un', 'deux', 'trois']]
|
||||||
|
, [2, 'hi', ['hello', 'world']]
|
||||||
|
, [2, ['hello', 'world'], 'hi']
|
||||||
|
, [2, ['hello' => 'world', 'herp' => 'derp']]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider callProvider
|
||||||
|
*/
|
||||||
|
public function testCall() {
|
||||||
|
$args = func_get_args();
|
||||||
|
$paramNum = array_shift($args);
|
||||||
|
|
||||||
|
$uri = 'http://example.com/endpoint/' . rand(1, 100);
|
||||||
|
$id = uniqid('', false);
|
||||||
|
$clientMessage = array_merge(array(2, $id, $uri), $args);
|
||||||
|
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode($clientMessage));
|
||||||
|
|
||||||
|
$this->assertEquals($id, $this->_app->last['onCall'][1]);
|
||||||
|
$this->assertEquals($uri, $this->_app->last['onCall'][2]);
|
||||||
|
|
||||||
|
$this->assertEquals($paramNum, count($this->_app->last['onCall'][3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPublish() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$topic = 'pubsubhubbub';
|
||||||
|
$event = 'Here I am, publishing data';
|
||||||
|
|
||||||
|
$clientMessage = array(7, $topic, $event);
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode($clientMessage));
|
||||||
|
|
||||||
|
$this->assertEquals($topic, $this->_app->last['onPublish'][1]);
|
||||||
|
$this->assertEquals($event, $this->_app->last['onPublish'][2]);
|
||||||
|
$this->assertEquals(array(), $this->_app->last['onPublish'][3]);
|
||||||
|
$this->assertEquals(array(), $this->_app->last['onPublish'][4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPublishAndExcludeMe() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', true)));
|
||||||
|
|
||||||
|
$this->assertEquals($conn->WAMP->sessionId, $this->_app->last['onPublish'][3][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPublishAndEligible() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
|
||||||
|
$buddy = uniqid('', false);
|
||||||
|
$friend = uniqid('', false);
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', false, array($buddy, $friend))));
|
||||||
|
|
||||||
|
$this->assertEquals(array(), $this->_app->last['onPublish'][3]);
|
||||||
|
$this->assertEquals(2, count($this->_app->last['onPublish'][4]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eventProvider() {
|
||||||
|
return array(
|
||||||
|
array('http://example.com', array('one', 'two'))
|
||||||
|
, array('curie', array(array('hello' => 'world', 'herp' => 'derp')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider eventProvider
|
||||||
|
*/
|
||||||
|
public function testEvent($topic, $payload) {
|
||||||
|
$conn = new WampConnection($this->newConn());
|
||||||
|
$conn->event($topic, $payload);
|
||||||
|
|
||||||
|
$eventString = $conn->last['send'];
|
||||||
|
|
||||||
|
$this->assertSame(array(8, $topic, $payload), json_decode($eventString, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnClosePropagation() {
|
||||||
|
$conn = new Connection;
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onClose($conn);
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('\\Ratchet\\Wamp\\WampConnection');
|
||||||
|
$method = $class->getMethod('getConnection');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$check = $method->invokeArgs($this->_app->last['onClose'][0], array());
|
||||||
|
|
||||||
|
$this->assertSame($conn, $check);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnErrorPropagation() {
|
||||||
|
$conn = new Connection;
|
||||||
|
|
||||||
|
$e = new \Exception('Nope');
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onError($conn, $e);
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('\\Ratchet\\Wamp\\WampConnection');
|
||||||
|
$method = $class->getMethod('getConnection');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$check = $method->invokeArgs($this->_app->last['onError'][0], array());
|
||||||
|
|
||||||
|
$this->assertSame($conn, $check);
|
||||||
|
$this->assertSame($e, $this->_app->last['onError'][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrefix() {
|
||||||
|
$conn = new WampConnection($this->newConn());
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
|
||||||
|
$prefix = 'incoming';
|
||||||
|
$fullURI = "http://example.com/$prefix";
|
||||||
|
$method = 'call';
|
||||||
|
|
||||||
|
$this->_comp->onMessage($conn, json_encode(array(1, $prefix, $fullURI)));
|
||||||
|
|
||||||
|
$this->assertEquals($fullURI, $conn->WAMP->prefixes[$prefix]);
|
||||||
|
$this->assertEquals("$fullURI#$method", $conn->getUri("$prefix:$method"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMessageMustBeJson() {
|
||||||
|
$this->setExpectedException('\\Ratchet\\Wamp\\JsonException');
|
||||||
|
|
||||||
|
$conn = new Connection;
|
||||||
|
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, 'Hello World!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsReturnsArray() {
|
||||||
|
$this->assertTrue(is_array($this->_comp->getSubProtocols()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsGetFromApp() {
|
||||||
|
$this->_app->protocols = array('hello', 'world');
|
||||||
|
|
||||||
|
$this->assertGreaterThanOrEqual(3, count($this->_comp->getSubProtocols()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWampOnMessageApp() {
|
||||||
|
$app = $this->getMock('\\Ratchet\\Wamp\\WampServerInterface');
|
||||||
|
$wamp = new ServerProtocol($app);
|
||||||
|
|
||||||
|
$this->assertContains('wamp', $wamp->getSubProtocols());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function badFormatProvider() {
|
||||||
|
return array(
|
||||||
|
array(json_encode(true))
|
||||||
|
, array('{"valid":"json", "invalid": "message"}')
|
||||||
|
, array('{"0": "fail", "hello": "world"}')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider badFormatProvider
|
||||||
|
*/
|
||||||
|
public function testValidJsonButInvalidProtocol($message) {
|
||||||
|
$this->setExpectedException('\Ratchet\Wamp\Exception');
|
||||||
|
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
$this->_comp->onMessage($conn, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBadClientInputFromNonStringTopic() {
|
||||||
|
$this->setExpectedException('\Ratchet\Wamp\Exception');
|
||||||
|
|
||||||
|
$conn = new WampConnection($this->newConn());
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
|
||||||
|
$this->_comp->onMessage($conn, json_encode([5, ['hells', 'nope']]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBadPrefixWithNonStringTopic() {
|
||||||
|
$this->setExpectedException('\Ratchet\Wamp\Exception');
|
||||||
|
|
||||||
|
$conn = new WampConnection($this->newConn());
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
|
||||||
|
$this->_comp->onMessage($conn, json_encode([1, ['hells', 'nope'], ['bad', 'input']]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBadPublishWithNonStringTopic() {
|
||||||
|
$this->setExpectedException('\Ratchet\Wamp\Exception');
|
||||||
|
|
||||||
|
$conn = new WampConnection($this->newConn());
|
||||||
|
$this->_comp->onOpen($conn);
|
||||||
|
|
||||||
|
$this->_comp->onMessage($conn, json_encode([7, ['bad', 'input'], 'Hider']));
|
||||||
|
}
|
||||||
|
}
|
||||||
226
src/vendor/cboden/ratchet/tests/unit/Wamp/TopicManagerTest.php
vendored
Normal file
226
src/vendor/cboden/ratchet/tests/unit/Wamp/TopicManagerTest.php
vendored
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Wamp\TopicManager
|
||||||
|
*/
|
||||||
|
class TopicManagerTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
private $mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\Wamp\TopicManager
|
||||||
|
*/
|
||||||
|
private $mngr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Ratchet\ConnectionInterface
|
||||||
|
*/
|
||||||
|
private $conn;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||||
|
$this->mock = $this->getMock('\Ratchet\Wamp\WampServerInterface');
|
||||||
|
$this->mngr = new TopicManager($this->mock);
|
||||||
|
|
||||||
|
$this->conn->WAMP = new \StdClass;
|
||||||
|
$this->mngr->onOpen($this->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTopicReturnsTopicObject() {
|
||||||
|
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
|
||||||
|
$method = $class->getMethod('getTopic');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$topic = $method->invokeArgs($this->mngr, array('The Topic'));
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Ratchet\Wamp\Topic', $topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTopicCreatesTopicWithSameName() {
|
||||||
|
$name = 'The Topic';
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
|
||||||
|
$method = $class->getMethod('getTopic');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$topic = $method->invokeArgs($this->mngr, array($name));
|
||||||
|
|
||||||
|
$this->assertEquals($name, $topic->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTopicReturnsSameObject() {
|
||||||
|
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
|
||||||
|
$method = $class->getMethod('getTopic');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$topic = $method->invokeArgs($this->mngr, array('No copy'));
|
||||||
|
$again = $method->invokeArgs($this->mngr, array('No copy'));
|
||||||
|
|
||||||
|
$this->assertSame($topic, $again);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnOpen() {
|
||||||
|
$this->mock->expects($this->once())->method('onOpen');
|
||||||
|
$this->mngr->onOpen($this->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnCall() {
|
||||||
|
$id = uniqid();
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('onCall')->with(
|
||||||
|
$this->conn
|
||||||
|
, $id
|
||||||
|
, $this->isInstanceOf('Ratchet\Wamp\Topic')
|
||||||
|
, array()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->mngr->onCall($this->conn, $id, 'new topic', array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnSubscribeCreatesTopicObject() {
|
||||||
|
$this->mock->expects($this->once())->method('onSubscribe')->with(
|
||||||
|
$this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, 'new topic');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTopicIsInConnectionOnSubscribe() {
|
||||||
|
$name = 'New Topic';
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
|
||||||
|
$method = $class->getMethod('getTopic');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$topic = $method->invokeArgs($this->mngr, array($name));
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, $name);
|
||||||
|
|
||||||
|
$this->assertTrue($this->conn->WAMP->subscriptions->contains($topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoubleSubscriptionFiresOnce() {
|
||||||
|
$this->mock->expects($this->exactly(1))->method('onSubscribe');
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, 'same topic');
|
||||||
|
$this->mngr->onSubscribe($this->conn, 'same topic');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnsubscribeEvent() {
|
||||||
|
$name = 'in and out';
|
||||||
|
$this->mock->expects($this->once())->method('onUnsubscribe')->with(
|
||||||
|
$this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, $name);
|
||||||
|
$this->mngr->onUnsubscribe($this->conn, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnsubscribeFiresOnce() {
|
||||||
|
$name = 'getting sleepy';
|
||||||
|
$this->mock->expects($this->exactly(1))->method('onUnsubscribe');
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, $name);
|
||||||
|
$this->mngr->onUnsubscribe($this->conn, $name);
|
||||||
|
$this->mngr->onUnsubscribe($this->conn, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnsubscribeRemovesTopicFromConnection() {
|
||||||
|
$name = 'Bye Bye Topic';
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
|
||||||
|
$method = $class->getMethod('getTopic');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$topic = $method->invokeArgs($this->mngr, array($name));
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, $name);
|
||||||
|
$this->mngr->onUnsubscribe($this->conn, $name);
|
||||||
|
|
||||||
|
$this->assertFalse($this->conn->WAMP->subscriptions->contains($topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnPublishBubbles() {
|
||||||
|
$msg = 'Cover all the code!';
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('onPublish')->with(
|
||||||
|
$this->conn
|
||||||
|
, $this->isInstanceOf('Ratchet\Wamp\Topic')
|
||||||
|
, $msg
|
||||||
|
, $this->isType('array')
|
||||||
|
, $this->isType('array')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->mngr->onPublish($this->conn, 'topic coverage', $msg, array(), array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnCloseBubbles() {
|
||||||
|
$this->mock->expects($this->once())->method('onClose')->with($this->conn);
|
||||||
|
$this->mngr->onClose($this->conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function topicProvider($name) {
|
||||||
|
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
|
||||||
|
$method = $class->getMethod('getTopic');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$attribute = $class->getProperty('topicLookup');
|
||||||
|
$attribute->setAccessible(true);
|
||||||
|
|
||||||
|
$topic = $method->invokeArgs($this->mngr, array($name));
|
||||||
|
|
||||||
|
return array($topic, $attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConnIsRemovedFromTopicOnClose() {
|
||||||
|
$name = 'State Testing';
|
||||||
|
list($topic, $attribute) = $this->topicProvider($name);
|
||||||
|
|
||||||
|
$this->assertCount(1, $attribute->getValue($this->mngr));
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, $name);
|
||||||
|
$this->mngr->onClose($this->conn);
|
||||||
|
|
||||||
|
$this->assertFalse($topic->has($this->conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function topicConnExpectationProvider() {
|
||||||
|
return [
|
||||||
|
[ 'onClose', 0]
|
||||||
|
, ['onUnsubscribe', 0]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider topicConnExpectationProvider
|
||||||
|
*/
|
||||||
|
public function testTopicRetentionFromLeavingConnections($methodCall, $expectation) {
|
||||||
|
$topicName = 'checkTopic';
|
||||||
|
list($topic, $attribute) = $this->topicProvider($topicName);
|
||||||
|
|
||||||
|
$this->mngr->onSubscribe($this->conn, $topicName);
|
||||||
|
call_user_func_array(array($this->mngr, $methodCall), array($this->conn, $topicName));
|
||||||
|
|
||||||
|
$this->assertCount($expectation, $attribute->getValue($this->mngr));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnErrorBubbles() {
|
||||||
|
$e = new \Exception('All work and no play makes Chris a dull boy');
|
||||||
|
$this->mock->expects($this->once())->method('onError')->with($this->conn, $e);
|
||||||
|
|
||||||
|
$this->mngr->onError($this->conn, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsReturnsArray() {
|
||||||
|
$this->assertInternalType('array', $this->mngr->getSubProtocols());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsBubbles() {
|
||||||
|
$subs = array('hello', 'world');
|
||||||
|
$app = $this->getMock('Ratchet\Wamp\Stub\WsWampServerInterface');
|
||||||
|
$app->expects($this->once())->method('getSubProtocols')->will($this->returnValue($subs));
|
||||||
|
$mngr = new TopicManager($app);
|
||||||
|
|
||||||
|
$this->assertEquals($subs, $mngr->getSubProtocols());
|
||||||
|
}
|
||||||
|
}
|
||||||
164
src/vendor/cboden/ratchet/tests/unit/Wamp/TopicTest.php
vendored
Normal file
164
src/vendor/cboden/ratchet/tests/unit/Wamp/TopicTest.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Wamp\Topic
|
||||||
|
*/
|
||||||
|
class TopicTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
public function testGetId() {
|
||||||
|
$id = uniqid();
|
||||||
|
$topic = new Topic($id);
|
||||||
|
|
||||||
|
$this->assertEquals($id, $topic->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddAndCount() {
|
||||||
|
$topic = new Topic('merp');
|
||||||
|
|
||||||
|
$topic->add($this->newConn());
|
||||||
|
$topic->add($this->newConn());
|
||||||
|
$topic->add($this->newConn());
|
||||||
|
|
||||||
|
$this->assertEquals(3, count($topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemove() {
|
||||||
|
$topic = new Topic('boop');
|
||||||
|
$tracked = $this->newConn();
|
||||||
|
|
||||||
|
$topic->add($this->newConn());
|
||||||
|
$topic->add($tracked);
|
||||||
|
$topic->add($this->newConn());
|
||||||
|
|
||||||
|
$topic->remove($tracked);
|
||||||
|
|
||||||
|
$this->assertEquals(2, count($topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBroadcast() {
|
||||||
|
$msg = 'Hello World!';
|
||||||
|
$name = 'Batman';
|
||||||
|
$protocol = json_encode(array(8, $name, $msg));
|
||||||
|
|
||||||
|
$first = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
$second = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
|
||||||
|
$first->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with($this->equalTo($protocol));
|
||||||
|
|
||||||
|
$second->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with($this->equalTo($protocol));
|
||||||
|
|
||||||
|
$topic = new Topic($name);
|
||||||
|
$topic->add($first);
|
||||||
|
$topic->add($second);
|
||||||
|
|
||||||
|
$topic->broadcast($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBroadcastWithExclude() {
|
||||||
|
$msg = 'Hello odd numbers';
|
||||||
|
$name = 'Excluding';
|
||||||
|
$protocol = json_encode(array(8, $name, $msg));
|
||||||
|
|
||||||
|
$first = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
$second = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
$third = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
|
||||||
|
$first->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with($this->equalTo($protocol));
|
||||||
|
|
||||||
|
$second->expects($this->never())->method('send');
|
||||||
|
|
||||||
|
$third->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with($this->equalTo($protocol));
|
||||||
|
|
||||||
|
$topic = new Topic($name);
|
||||||
|
$topic->add($first);
|
||||||
|
$topic->add($second);
|
||||||
|
$topic->add($third);
|
||||||
|
|
||||||
|
$topic->broadcast($msg, array($second->WAMP->sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBroadcastWithEligible() {
|
||||||
|
$msg = 'Hello white list';
|
||||||
|
$name = 'Eligible';
|
||||||
|
$protocol = json_encode(array(8, $name, $msg));
|
||||||
|
|
||||||
|
$first = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
$second = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
$third = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface')));
|
||||||
|
|
||||||
|
$first->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with($this->equalTo($protocol));
|
||||||
|
|
||||||
|
$second->expects($this->never())->method('send');
|
||||||
|
|
||||||
|
$third->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->with($this->equalTo($protocol));
|
||||||
|
|
||||||
|
$topic = new Topic($name);
|
||||||
|
$topic->add($first);
|
||||||
|
$topic->add($second);
|
||||||
|
$topic->add($third);
|
||||||
|
|
||||||
|
$topic->broadcast($msg, array(), array($first->WAMP->sessionId, $third->WAMP->sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIterator() {
|
||||||
|
$first = $this->newConn();
|
||||||
|
$second = $this->newConn();
|
||||||
|
$third = $this->newConn();
|
||||||
|
|
||||||
|
$topic = new Topic('Joker');
|
||||||
|
$topic->add($first)->add($second)->add($third);
|
||||||
|
|
||||||
|
$check = array($first, $second, $third);
|
||||||
|
|
||||||
|
foreach ($topic as $mock) {
|
||||||
|
$this->assertNotSame(false, array_search($mock, $check));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToString() {
|
||||||
|
$name = 'Bane';
|
||||||
|
$topic = new Topic($name);
|
||||||
|
|
||||||
|
$this->assertEquals($name, (string)$topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoesHave() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$topic = new Topic('Two Face');
|
||||||
|
$topic->add($conn);
|
||||||
|
|
||||||
|
$this->assertTrue($topic->has($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoesNotHave() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$topic = new Topic('Alfred');
|
||||||
|
|
||||||
|
$this->assertFalse($topic->has($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoesNotHaveAfterRemove() {
|
||||||
|
$conn = $this->newConn();
|
||||||
|
$topic = new Topic('Ras');
|
||||||
|
|
||||||
|
$topic->add($conn)->remove($conn);
|
||||||
|
|
||||||
|
$this->assertFalse($topic->has($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newConn() {
|
||||||
|
return new WampConnection($this->getMock('\\Ratchet\\ConnectionInterface'));
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/vendor/cboden/ratchet/tests/unit/Wamp/WampConnectionTest.php
vendored
Normal file
77
src/vendor/cboden/ratchet/tests/unit/Wamp/WampConnectionTest.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Wamp\WampConnection
|
||||||
|
*/
|
||||||
|
class WampConnectionTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $conn;
|
||||||
|
protected $mock;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->mock = $this->getMock('\\Ratchet\\ConnectionInterface');
|
||||||
|
$this->conn = new WampConnection($this->mock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCallResult() {
|
||||||
|
$callId = uniqid();
|
||||||
|
$data = array('hello' => 'world', 'herp' => 'derp');
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('send')->with(json_encode(array(3, $callId, $data)));
|
||||||
|
|
||||||
|
$this->conn->callResult($callId, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCallError() {
|
||||||
|
$callId = uniqid();
|
||||||
|
$uri = 'http://example.com/end/point';
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, '')));
|
||||||
|
|
||||||
|
$this->conn->callError($callId, $uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCallErrorWithTopic() {
|
||||||
|
$callId = uniqid();
|
||||||
|
$uri = 'http://example.com/end/point';
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, '')));
|
||||||
|
|
||||||
|
$this->conn->callError($callId, new Topic($uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDetailedCallError() {
|
||||||
|
$callId = uniqid();
|
||||||
|
$uri = 'http://example.com/end/point';
|
||||||
|
$desc = 'beep boop beep';
|
||||||
|
$detail = 'Error: Too much awesome';
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, $desc, $detail)));
|
||||||
|
|
||||||
|
$this->conn->callError($callId, $uri, $desc, $detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrefix() {
|
||||||
|
$shortOut = 'outgoing';
|
||||||
|
$longOut = 'http://example.com/outgoing';
|
||||||
|
|
||||||
|
$this->mock->expects($this->once())->method('send')->with(json_encode(array(1, $shortOut, $longOut)));
|
||||||
|
|
||||||
|
$this->conn->prefix($shortOut, $longOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetUriWhenNoCurieGiven() {
|
||||||
|
$uri = 'http://example.com/noshort';
|
||||||
|
|
||||||
|
$this->assertEquals($uri, $this->conn->getUri($uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClose() {
|
||||||
|
$mock = $this->getMock('\\Ratchet\\ConnectionInterface');
|
||||||
|
$conn = new WampConnection($mock);
|
||||||
|
|
||||||
|
$mock->expects($this->once())->method('close');
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/vendor/cboden/ratchet/tests/unit/Wamp/WampServerTest.php
vendored
Normal file
49
src/vendor/cboden/ratchet/tests/unit/Wamp/WampServerTest.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\AbstractMessageComponentTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\Wamp\WampServer
|
||||||
|
*/
|
||||||
|
class WampServerTest extends AbstractMessageComponentTestCase {
|
||||||
|
public function getConnectionClassString() {
|
||||||
|
return '\Ratchet\Wamp\WampConnection';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecoratorClassString() {
|
||||||
|
return 'Ratchet\Wamp\WampServer';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentClassString() {
|
||||||
|
return '\Ratchet\Wamp\WampServerInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnMessageToEvent() {
|
||||||
|
$published = 'Client published this message';
|
||||||
|
|
||||||
|
$this->_app->expects($this->once())->method('onPublish')->with(
|
||||||
|
$this->isExpectedConnection()
|
||||||
|
, new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\Wamp\Topic')
|
||||||
|
, $published
|
||||||
|
, array()
|
||||||
|
, array()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_serv->onMessage($this->_conn, json_encode(array(7, 'topic', $published)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocols() {
|
||||||
|
// todo: could expand on this
|
||||||
|
$this->assertInternalType('array', $this->_serv->getSubProtocols());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConnectionClosesOnInvalidJson() {
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
$this->_serv->onMessage($this->_conn, 'invalid json');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConnectionClosesOnProtocolError() {
|
||||||
|
$this->_conn->expects($this->once())->method('close');
|
||||||
|
$this->_serv->onMessage($this->_conn, json_encode(array('valid' => 'json', 'invalid' => 'protocol')));
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/vendor/composer/autoload_files.php
vendored
3
src/vendor/composer/autoload_files.php
vendored
@@ -11,8 +11,9 @@ return array(
|
|||||||
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
|
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||||
'9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
|
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
|
'9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
|
||||||
|
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
|
||||||
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
|
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
|
||||||
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
||||||
'cd5441689b14144e5573bf989ee47b34' => $vendorDir . '/qcloud/cos-sdk-v5/src/Common.php',
|
'cd5441689b14144e5573bf989ee47b34' => $vendorDir . '/qcloud/cos-sdk-v5/src/Common.php',
|
||||||
|
|||||||
10
src/vendor/composer/autoload_psr4.php
vendored
10
src/vendor/composer/autoload_psr4.php
vendored
@@ -34,11 +34,20 @@ return array(
|
|||||||
'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'),
|
'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'),
|
||||||
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
|
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
|
||||||
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
|
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
|
||||||
|
'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'),
|
||||||
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
|
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
|
||||||
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
|
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
|
||||||
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
|
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
|
||||||
'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'),
|
'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'),
|
||||||
'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'),
|
'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'),
|
||||||
|
'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
|
||||||
|
'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
|
||||||
|
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
|
||||||
|
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
|
||||||
|
'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
|
||||||
|
'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
|
||||||
|
'Ratchet\\RFC6455\\' => array($vendorDir . '/ratchet/rfc6455/src'),
|
||||||
|
'Ratchet\\' => array($vendorDir . '/cboden/ratchet/src/Ratchet'),
|
||||||
'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'),
|
'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'),
|
||||||
'Qcloud\\Cos\\' => array($vendorDir . '/qcloud/cos-sdk-v5/src'),
|
'Qcloud\\Cos\\' => array($vendorDir . '/qcloud/cos-sdk-v5/src'),
|
||||||
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
|
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
|
||||||
@@ -73,6 +82,7 @@ return array(
|
|||||||
'GuzzleHttp\\Command\\' => array($vendorDir . '/guzzlehttp/command/src'),
|
'GuzzleHttp\\Command\\' => array($vendorDir . '/guzzlehttp/command/src'),
|
||||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||||
'GatewayClient\\' => array($vendorDir . '/workerman/gatewayclient'),
|
'GatewayClient\\' => array($vendorDir . '/workerman/gatewayclient'),
|
||||||
|
'Evenement\\' => array($vendorDir . '/evenement/evenement/src'),
|
||||||
'EasyWeChat\\' => array($vendorDir . '/overtrue/wechat/src'),
|
'EasyWeChat\\' => array($vendorDir . '/overtrue/wechat/src'),
|
||||||
'EasyWeChatComposer\\' => array($vendorDir . '/easywechat-composer/easywechat-composer/src'),
|
'EasyWeChatComposer\\' => array($vendorDir . '/easywechat-composer/easywechat-composer/src'),
|
||||||
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
|
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
|
||||||
|
|||||||
56
src/vendor/composer/autoload_static.php
vendored
56
src/vendor/composer/autoload_static.php
vendored
@@ -12,8 +12,9 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
|
|||||||
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
|
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||||
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
|
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
|
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
|
||||||
|
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
|
||||||
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
|
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
|
||||||
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
|
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
|
||||||
'cd5441689b14144e5573bf989ee47b34' => __DIR__ . '/..' . '/qcloud/cos-sdk-v5/src/Common.php',
|
'cd5441689b14144e5573bf989ee47b34' => __DIR__ . '/..' . '/qcloud/cos-sdk-v5/src/Common.php',
|
||||||
@@ -91,12 +92,24 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
|
|||||||
'Symfony\\Component\\VarExporter\\' => 30,
|
'Symfony\\Component\\VarExporter\\' => 30,
|
||||||
'Symfony\\Component\\VarDumper\\' => 28,
|
'Symfony\\Component\\VarDumper\\' => 28,
|
||||||
'Symfony\\Component\\Translation\\' => 30,
|
'Symfony\\Component\\Translation\\' => 30,
|
||||||
|
'Symfony\\Component\\Routing\\' => 26,
|
||||||
'Symfony\\Component\\Process\\' => 26,
|
'Symfony\\Component\\Process\\' => 26,
|
||||||
'Symfony\\Component\\HttpFoundation\\' => 33,
|
'Symfony\\Component\\HttpFoundation\\' => 33,
|
||||||
'Symfony\\Component\\EventDispatcher\\' => 34,
|
'Symfony\\Component\\EventDispatcher\\' => 34,
|
||||||
'Symfony\\Component\\Cache\\' => 24,
|
'Symfony\\Component\\Cache\\' => 24,
|
||||||
'Symfony\\Bridge\\PsrHttpMessage\\' => 30,
|
'Symfony\\Bridge\\PsrHttpMessage\\' => 30,
|
||||||
),
|
),
|
||||||
|
'R' =>
|
||||||
|
array (
|
||||||
|
'React\\Stream\\' => 13,
|
||||||
|
'React\\Socket\\' => 13,
|
||||||
|
'React\\Promise\\' => 14,
|
||||||
|
'React\\EventLoop\\' => 16,
|
||||||
|
'React\\Dns\\' => 10,
|
||||||
|
'React\\Cache\\' => 12,
|
||||||
|
'Ratchet\\RFC6455\\' => 16,
|
||||||
|
'Ratchet\\' => 8,
|
||||||
|
),
|
||||||
'Q' =>
|
'Q' =>
|
||||||
array (
|
array (
|
||||||
'Qiniu\\' => 6,
|
'Qiniu\\' => 6,
|
||||||
@@ -154,6 +167,7 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
|
|||||||
),
|
),
|
||||||
'E' =>
|
'E' =>
|
||||||
array (
|
array (
|
||||||
|
'Evenement\\' => 10,
|
||||||
'EasyWeChat\\' => 11,
|
'EasyWeChat\\' => 11,
|
||||||
'EasyWeChatComposer\\' => 19,
|
'EasyWeChatComposer\\' => 19,
|
||||||
),
|
),
|
||||||
@@ -291,6 +305,10 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
|
|||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/translation',
|
0 => __DIR__ . '/..' . '/symfony/translation',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Component\\Routing\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/routing',
|
||||||
|
),
|
||||||
'Symfony\\Component\\Process\\' =>
|
'Symfony\\Component\\Process\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/process',
|
0 => __DIR__ . '/..' . '/symfony/process',
|
||||||
@@ -311,6 +329,38 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
|
|||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge',
|
0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge',
|
||||||
),
|
),
|
||||||
|
'React\\Stream\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/react/stream/src',
|
||||||
|
),
|
||||||
|
'React\\Socket\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/react/socket/src',
|
||||||
|
),
|
||||||
|
'React\\Promise\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/react/promise/src',
|
||||||
|
),
|
||||||
|
'React\\EventLoop\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/react/event-loop/src',
|
||||||
|
),
|
||||||
|
'React\\Dns\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/react/dns/src',
|
||||||
|
),
|
||||||
|
'React\\Cache\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/react/cache/src',
|
||||||
|
),
|
||||||
|
'Ratchet\\RFC6455\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/ratchet/rfc6455/src',
|
||||||
|
),
|
||||||
|
'Ratchet\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/cboden/ratchet/src/Ratchet',
|
||||||
|
),
|
||||||
'Qiniu\\' =>
|
'Qiniu\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu',
|
0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu',
|
||||||
@@ -448,6 +498,10 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
|
|||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/workerman/gatewayclient',
|
0 => __DIR__ . '/..' . '/workerman/gatewayclient',
|
||||||
),
|
),
|
||||||
|
'Evenement\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/evenement/evenement/src',
|
||||||
|
),
|
||||||
'EasyWeChat\\' =>
|
'EasyWeChat\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/overtrue/wechat/src',
|
0 => __DIR__ . '/..' . '/overtrue/wechat/src',
|
||||||
|
|||||||
738
src/vendor/composer/installed.json
vendored
738
src/vendor/composer/installed.json
vendored
@@ -48,6 +48,72 @@
|
|||||||
},
|
},
|
||||||
"install-path": "../aliyuncs/oss-sdk-php"
|
"install-path": "../aliyuncs/oss-sdk-php"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "cboden/ratchet",
|
||||||
|
"version": "v0.4.4",
|
||||||
|
"version_normalized": "0.4.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ratchetphp/Ratchet.git",
|
||||||
|
"reference": "5012dc954541b40c5599d286fd40653f5716a38f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/5012dc954541b40c5599d286fd40653f5716a38f",
|
||||||
|
"reference": "5012dc954541b40c5599d286fd40653f5716a38f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"guzzlehttp/psr7": "^1.7|^2.0",
|
||||||
|
"php": ">=5.4.2",
|
||||||
|
"ratchet/rfc6455": "^0.3.1",
|
||||||
|
"react/event-loop": ">=0.4",
|
||||||
|
"react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5",
|
||||||
|
"symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0",
|
||||||
|
"symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~4.8"
|
||||||
|
},
|
||||||
|
"time": "2021-12-14T00:20:41+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ratchet\\": "src/Ratchet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Bonneau",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP WebSocket library",
|
||||||
|
"homepage": "http://socketo.me",
|
||||||
|
"keywords": [
|
||||||
|
"Ratchet",
|
||||||
|
"WebSockets",
|
||||||
|
"server",
|
||||||
|
"sockets",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://gitter.im/reactphp/reactphp",
|
||||||
|
"issues": "https://github.com/ratchetphp/Ratchet/issues",
|
||||||
|
"source": "https://github.com/ratchetphp/Ratchet/tree/v0.4.4"
|
||||||
|
},
|
||||||
|
"install-path": "../cboden/ratchet"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
@@ -290,6 +356,56 @@
|
|||||||
},
|
},
|
||||||
"install-path": "../easywechat-composer/easywechat-composer"
|
"install-path": "../easywechat-composer/easywechat-composer"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "evenement/evenement",
|
||||||
|
"version": "v3.0.2",
|
||||||
|
"version_normalized": "3.0.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/igorw/evenement.git",
|
||||||
|
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
|
||||||
|
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9 || ^6"
|
||||||
|
},
|
||||||
|
"time": "2023-08-08T05:53:35+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Evenement\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Igor Wiedler",
|
||||||
|
"email": "igor@wiedler.ch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Événement is a very simple event dispatching library for PHP",
|
||||||
|
"keywords": [
|
||||||
|
"event-dispatcher",
|
||||||
|
"event-emitter"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/igorw/evenement/issues",
|
||||||
|
"source": "https://github.com/igorw/evenement/tree/v3.0.2"
|
||||||
|
},
|
||||||
|
"install-path": "../evenement/evenement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ezyang/htmlpurifier",
|
"name": "ezyang/htmlpurifier",
|
||||||
"version": "v4.19.0",
|
"version": "v4.19.0",
|
||||||
@@ -4213,6 +4329,535 @@
|
|||||||
},
|
},
|
||||||
"install-path": "../ralouphie/getallheaders"
|
"install-path": "../ralouphie/getallheaders"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ratchet/rfc6455",
|
||||||
|
"version": "v0.3.1",
|
||||||
|
"version_normalized": "0.3.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ratchetphp/RFC6455.git",
|
||||||
|
"reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb",
|
||||||
|
"reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"guzzlehttp/psr7": "^2 || ^1.7",
|
||||||
|
"php": ">=5.4.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^5.7",
|
||||||
|
"react/socket": "^1.3"
|
||||||
|
},
|
||||||
|
"time": "2021-12-09T23:20:49+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ratchet\\RFC6455\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Bonneau",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "RFC6455 WebSocket protocol handler",
|
||||||
|
"homepage": "http://socketo.me",
|
||||||
|
"keywords": [
|
||||||
|
"WebSockets",
|
||||||
|
"rfc6455",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://gitter.im/reactphp/reactphp",
|
||||||
|
"issues": "https://github.com/ratchetphp/RFC6455/issues",
|
||||||
|
"source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1"
|
||||||
|
},
|
||||||
|
"install-path": "../ratchet/rfc6455"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/cache",
|
||||||
|
"version": "v1.2.0",
|
||||||
|
"version_normalized": "1.2.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/cache.git",
|
||||||
|
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
|
||||||
|
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"react/promise": "^3.0 || ^2.0 || ^1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
|
||||||
|
},
|
||||||
|
"time": "2022-11-30T15:59:55+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Cache\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Async, Promise-based cache interface for ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"cache",
|
||||||
|
"caching",
|
||||||
|
"promise",
|
||||||
|
"reactphp"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/cache/issues",
|
||||||
|
"source": "https://github.com/reactphp/cache/tree/v1.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../react/cache"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/dns",
|
||||||
|
"version": "v1.14.0",
|
||||||
|
"version_normalized": "1.14.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/dns.git",
|
||||||
|
"reference": "7562c05391f42701c1fccf189c8225fece1cd7c3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3",
|
||||||
|
"reference": "7562c05391f42701c1fccf189c8225fece1cd7c3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
||||||
|
"react/event-loop": "^1.2",
|
||||||
|
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||||
|
"react/async": "^4.3 || ^3 || ^2",
|
||||||
|
"react/promise-timer": "^1.11"
|
||||||
|
},
|
||||||
|
"time": "2025-11-18T19:34:28+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Dns\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Async DNS resolver for ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"async",
|
||||||
|
"dns",
|
||||||
|
"dns-resolver",
|
||||||
|
"reactphp"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/dns/issues",
|
||||||
|
"source": "https://github.com/reactphp/dns/tree/v1.14.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../react/dns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/event-loop",
|
||||||
|
"version": "v1.6.0",
|
||||||
|
"version_normalized": "1.6.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/event-loop.git",
|
||||||
|
"reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
|
||||||
|
"reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
|
||||||
|
},
|
||||||
|
"time": "2025-11-17T20:46:25+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\EventLoop\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
|
||||||
|
"keywords": [
|
||||||
|
"asynchronous",
|
||||||
|
"event-loop"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/event-loop/issues",
|
||||||
|
"source": "https://github.com/reactphp/event-loop/tree/v1.6.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../react/event-loop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/promise",
|
||||||
|
"version": "v3.3.0",
|
||||||
|
"version_normalized": "3.3.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/promise.git",
|
||||||
|
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||||
|
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "1.12.28 || 1.4.10",
|
||||||
|
"phpunit/phpunit": "^9.6 || ^7.5"
|
||||||
|
},
|
||||||
|
"time": "2025-08-19T18:57:03+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions_include.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Promise\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||||
|
"keywords": [
|
||||||
|
"promise",
|
||||||
|
"promises"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/promise/issues",
|
||||||
|
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../react/promise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/socket",
|
||||||
|
"version": "v1.17.0",
|
||||||
|
"version_normalized": "1.17.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/socket.git",
|
||||||
|
"reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08",
|
||||||
|
"reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"react/dns": "^1.13",
|
||||||
|
"react/event-loop": "^1.2",
|
||||||
|
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
|
||||||
|
"react/stream": "^1.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||||
|
"react/async": "^4.3 || ^3.3 || ^2",
|
||||||
|
"react/promise-stream": "^1.4",
|
||||||
|
"react/promise-timer": "^1.11"
|
||||||
|
},
|
||||||
|
"time": "2025-11-19T20:47:34+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Socket\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"Connection",
|
||||||
|
"Socket",
|
||||||
|
"async",
|
||||||
|
"reactphp",
|
||||||
|
"stream"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/socket/issues",
|
||||||
|
"source": "https://github.com/reactphp/socket/tree/v1.17.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../react/socket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "react/stream",
|
||||||
|
"version": "v1.4.0",
|
||||||
|
"version_normalized": "1.4.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/stream.git",
|
||||||
|
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
|
||||||
|
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||||
|
"php": ">=5.3.8",
|
||||||
|
"react/event-loop": "^1.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"clue/stream-filter": "~1.2",
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||||
|
},
|
||||||
|
"time": "2024-06-11T12:45:25+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"React\\Stream\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering",
|
||||||
|
"homepage": "https://clue.engineering/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cees-Jan Kiewiet",
|
||||||
|
"email": "reactphp@ceesjankiewiet.nl",
|
||||||
|
"homepage": "https://wyrihaximus.net/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jan Sorgalla",
|
||||||
|
"email": "jsorgalla@gmail.com",
|
||||||
|
"homepage": "https://sorgalla.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chris Boden",
|
||||||
|
"email": "cboden@gmail.com",
|
||||||
|
"homepage": "https://cboden.dev/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
|
||||||
|
"keywords": [
|
||||||
|
"event-driven",
|
||||||
|
"io",
|
||||||
|
"non-blocking",
|
||||||
|
"pipe",
|
||||||
|
"reactphp",
|
||||||
|
"readable",
|
||||||
|
"stream",
|
||||||
|
"writable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/reactphp/stream/issues",
|
||||||
|
"source": "https://github.com/reactphp/stream/tree/v1.4.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/reactphp",
|
||||||
|
"type": "open_collective"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../react/stream"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/code-unit-reverse-lookup",
|
"name": "sebastian/code-unit-reverse-lookup",
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@@ -6056,6 +6701,99 @@
|
|||||||
],
|
],
|
||||||
"install-path": "../symfony/psr-http-message-bridge"
|
"install-path": "../symfony/psr-http-message-bridge"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/routing",
|
||||||
|
"version": "v5.4.48",
|
||||||
|
"version_normalized": "5.4.48.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/routing.git",
|
||||||
|
"reference": "dd08c19879a9b37ff14fd30dcbdf99a4cf045db1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/routing/zipball/dd08c19879a9b37ff14fd30dcbdf99a4cf045db1",
|
||||||
|
"reference": "dd08c19879a9b37ff14fd30dcbdf99a4cf045db1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/deprecation-contracts": "^2.1|^3",
|
||||||
|
"symfony/polyfill-php80": "^1.16"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/annotations": "<1.12",
|
||||||
|
"symfony/config": "<5.3",
|
||||||
|
"symfony/dependency-injection": "<4.4",
|
||||||
|
"symfony/yaml": "<4.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/annotations": "^1.12|^2",
|
||||||
|
"psr/log": "^1|^2|^3",
|
||||||
|
"symfony/config": "^5.3|^6.0",
|
||||||
|
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
|
||||||
|
"symfony/expression-language": "^4.4|^5.0|^6.0",
|
||||||
|
"symfony/http-foundation": "^4.4|^5.0|^6.0",
|
||||||
|
"symfony/yaml": "^4.4|^5.0|^6.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/config": "For using the all-in-one router or any loader",
|
||||||
|
"symfony/expression-language": "For using expression matching",
|
||||||
|
"symfony/http-foundation": "For using a Symfony Request object",
|
||||||
|
"symfony/yaml": "For using the YAML loader"
|
||||||
|
},
|
||||||
|
"time": "2024-11-12T18:20:21+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Routing\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Maps an HTTP request to a set of configuration variables",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"router",
|
||||||
|
"routing",
|
||||||
|
"uri",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/routing/tree/v5.4.48"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../symfony/routing"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/service-contracts",
|
"name": "symfony/service-contracts",
|
||||||
"version": "v2.5.4",
|
"version": "v2.5.4",
|
||||||
|
|||||||
94
src/vendor/composer/installed.php
vendored
94
src/vendor/composer/installed.php
vendored
@@ -3,7 +3,7 @@
|
|||||||
'name' => 'topthink/think',
|
'name' => 'topthink/think',
|
||||||
'pretty_version' => 'dev-main',
|
'pretty_version' => 'dev-main',
|
||||||
'version' => 'dev-main',
|
'version' => 'dev-main',
|
||||||
'reference' => '54ef5ccf3d11ab12c094632e63a22b55dfee2495',
|
'reference' => '3239891cd1c88847ccb9f4ba1c72b37723e17724',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
@@ -19,6 +19,15 @@
|
|||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'cboden/ratchet' => array(
|
||||||
|
'pretty_version' => 'v0.4.4',
|
||||||
|
'version' => '0.4.4.0',
|
||||||
|
'reference' => '5012dc954541b40c5599d286fd40653f5716a38f',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../cboden/ratchet',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'doctrine/deprecations' => array(
|
'doctrine/deprecations' => array(
|
||||||
'pretty_version' => '1.1.5',
|
'pretty_version' => '1.1.5',
|
||||||
'version' => '1.1.5.0',
|
'version' => '1.1.5.0',
|
||||||
@@ -55,6 +64,15 @@
|
|||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'evenement/evenement' => array(
|
||||||
|
'pretty_version' => 'v3.0.2',
|
||||||
|
'version' => '3.0.2.0',
|
||||||
|
'reference' => '0a16b0d71ab13284339abb99d9d2bd813640efbc',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../evenement/evenement',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'ezyang/htmlpurifier' => array(
|
'ezyang/htmlpurifier' => array(
|
||||||
'pretty_version' => 'v4.19.0',
|
'pretty_version' => 'v4.19.0',
|
||||||
'version' => '4.19.0.0',
|
'version' => '4.19.0.0',
|
||||||
@@ -610,6 +628,69 @@
|
|||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'ratchet/rfc6455' => array(
|
||||||
|
'pretty_version' => 'v0.3.1',
|
||||||
|
'version' => '0.3.1.0',
|
||||||
|
'reference' => '7c964514e93456a52a99a20fcfa0de242a43ccdb',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../ratchet/rfc6455',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'react/cache' => array(
|
||||||
|
'pretty_version' => 'v1.2.0',
|
||||||
|
'version' => '1.2.0.0',
|
||||||
|
'reference' => 'd47c472b64aa5608225f47965a484b75c7817d5b',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../react/cache',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'react/dns' => array(
|
||||||
|
'pretty_version' => 'v1.14.0',
|
||||||
|
'version' => '1.14.0.0',
|
||||||
|
'reference' => '7562c05391f42701c1fccf189c8225fece1cd7c3',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../react/dns',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'react/event-loop' => array(
|
||||||
|
'pretty_version' => 'v1.6.0',
|
||||||
|
'version' => '1.6.0.0',
|
||||||
|
'reference' => 'ba276bda6083df7e0050fd9b33f66ad7a4ac747a',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../react/event-loop',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'react/promise' => array(
|
||||||
|
'pretty_version' => 'v3.3.0',
|
||||||
|
'version' => '3.3.0.0',
|
||||||
|
'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../react/promise',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'react/socket' => array(
|
||||||
|
'pretty_version' => 'v1.17.0',
|
||||||
|
'version' => '1.17.0.0',
|
||||||
|
'reference' => 'ef5b17b81f6f60504c539313f94f2d826c5faa08',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../react/socket',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'react/stream' => array(
|
||||||
|
'pretty_version' => 'v1.4.0',
|
||||||
|
'version' => '1.4.0.0',
|
||||||
|
'reference' => '1e5b0acb8fe55143b5b426817155190eb6f5b18d',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../react/stream',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'sebastian/code-unit-reverse-lookup' => array(
|
'sebastian/code-unit-reverse-lookup' => array(
|
||||||
'pretty_version' => '1.0.3',
|
'pretty_version' => '1.0.3',
|
||||||
'version' => '1.0.3.0',
|
'version' => '1.0.3.0',
|
||||||
@@ -847,6 +928,15 @@
|
|||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'symfony/routing' => array(
|
||||||
|
'pretty_version' => 'v5.4.48',
|
||||||
|
'version' => '5.4.48.0',
|
||||||
|
'reference' => 'dd08c19879a9b37ff14fd30dcbdf99a4cf045db1',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/routing',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'symfony/service-contracts' => array(
|
'symfony/service-contracts' => array(
|
||||||
'pretty_version' => 'v2.5.4',
|
'pretty_version' => 'v2.5.4',
|
||||||
'version' => '2.5.4.0',
|
'version' => '2.5.4.0',
|
||||||
@@ -928,7 +1018,7 @@
|
|||||||
'topthink/think' => array(
|
'topthink/think' => array(
|
||||||
'pretty_version' => 'dev-main',
|
'pretty_version' => 'dev-main',
|
||||||
'version' => 'dev-main',
|
'version' => 'dev-main',
|
||||||
'reference' => '54ef5ccf3d11ab12c094632e63a22b55dfee2495',
|
'reference' => '3239891cd1c88847ccb9f4ba1c72b37723e17724',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|||||||
7
src/vendor/evenement/evenement/.gitattributes
vendored
Normal file
7
src/vendor/evenement/evenement/.gitattributes
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/.github export-ignore
|
||||||
|
/doc export-ignore
|
||||||
|
/examples export-ignore
|
||||||
|
/tests export-ignore
|
||||||
|
/.gitignore export-ignore
|
||||||
|
/CHANGELOG.md export-ignore
|
||||||
|
/phpunit.xml.dist export-ignore
|
||||||
19
src/vendor/evenement/evenement/LICENSE
vendored
Normal file
19
src/vendor/evenement/evenement/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2011 Igor Wiedler
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
64
src/vendor/evenement/evenement/README.md
vendored
Normal file
64
src/vendor/evenement/evenement/README.md
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Événement
|
||||||
|
|
||||||
|
Événement is a very simple event dispatching library for PHP.
|
||||||
|
|
||||||
|
It has the same design goals as [Silex](https://silex.symfony.com/) and
|
||||||
|
[Pimple](https://github.com/silexphp/Pimple), to empower the user while staying concise
|
||||||
|
and simple.
|
||||||
|
|
||||||
|
It is very strongly inspired by the [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) API found in
|
||||||
|
[node.js](http://nodejs.org).
|
||||||
|
|
||||||
|

|
||||||
|
[](https://packagist.org/packages/evenement/evenement)
|
||||||
|
[](https://packagist.org/packages/evenement/evenement/stats)
|
||||||
|
[](https://packagist.org/packages/evenement/evenement)
|
||||||
|
|
||||||
|
## Fetch
|
||||||
|
|
||||||
|
The recommended way to install Événement is [through composer](http://getcomposer.org). By running the following command:
|
||||||
|
|
||||||
|
$ composer require evenement/evenement
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Creating an Emitter
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$emitter = new Evenement\EventEmitter();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Listeners
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$emitter->on('user.created', function (User $user) use ($logger) {
|
||||||
|
$logger->log(sprintf("User '%s' was created.", $user->getLogin()));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Removing Listeners
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$emitter->removeListener('user.created', function (User $user) use ($logger) {
|
||||||
|
$logger->log(sprintf("User '%s' was created.", $user->getLogin()));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emitting Events
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$emitter->emit('user.created', [$user]);
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
$ ./vendor/bin/phpunit
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
MIT, see LICENSE.
|
||||||
29
src/vendor/evenement/evenement/composer.json
vendored
Normal file
29
src/vendor/evenement/evenement/composer.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "evenement/evenement",
|
||||||
|
"description": "Événement is a very simple event dispatching library for PHP",
|
||||||
|
"keywords": ["event-dispatcher", "event-emitter"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Igor Wiedler",
|
||||||
|
"email": "igor@wiedler.ch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9 || ^6"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Evenement\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Evenement\\Tests\\": "tests/"
|
||||||
|
},
|
||||||
|
"files": ["tests/functions.php"]
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/vendor/evenement/evenement/src/EventEmitter.php
vendored
Normal file
17
src/vendor/evenement/evenement/src/EventEmitter.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Evenement.
|
||||||
|
*
|
||||||
|
* (c) Igor Wiedler <igor@wiedler.ch>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Evenement;
|
||||||
|
|
||||||
|
class EventEmitter implements EventEmitterInterface
|
||||||
|
{
|
||||||
|
use EventEmitterTrait;
|
||||||
|
}
|
||||||
22
src/vendor/evenement/evenement/src/EventEmitterInterface.php
vendored
Normal file
22
src/vendor/evenement/evenement/src/EventEmitterInterface.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Evenement.
|
||||||
|
*
|
||||||
|
* (c) Igor Wiedler <igor@wiedler.ch>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Evenement;
|
||||||
|
|
||||||
|
interface EventEmitterInterface
|
||||||
|
{
|
||||||
|
public function on($event, callable $listener);
|
||||||
|
public function once($event, callable $listener);
|
||||||
|
public function removeListener($event, callable $listener);
|
||||||
|
public function removeAllListeners($event = null);
|
||||||
|
public function listeners($event = null);
|
||||||
|
public function emit($event, array $arguments = []);
|
||||||
|
}
|
||||||
154
src/vendor/evenement/evenement/src/EventEmitterTrait.php
vendored
Normal file
154
src/vendor/evenement/evenement/src/EventEmitterTrait.php
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Evenement.
|
||||||
|
*
|
||||||
|
* (c) Igor Wiedler <igor@wiedler.ch>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Evenement;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
use function count;
|
||||||
|
use function array_keys;
|
||||||
|
use function array_merge;
|
||||||
|
use function array_search;
|
||||||
|
use function array_unique;
|
||||||
|
use function array_values;
|
||||||
|
|
||||||
|
trait EventEmitterTrait
|
||||||
|
{
|
||||||
|
protected $listeners = [];
|
||||||
|
protected $onceListeners = [];
|
||||||
|
|
||||||
|
public function on($event, callable $listener)
|
||||||
|
{
|
||||||
|
if ($event === null) {
|
||||||
|
throw new InvalidArgumentException('event name must not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->listeners[$event])) {
|
||||||
|
$this->listeners[$event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->listeners[$event][] = $listener;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function once($event, callable $listener)
|
||||||
|
{
|
||||||
|
if ($event === null) {
|
||||||
|
throw new InvalidArgumentException('event name must not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->onceListeners[$event])) {
|
||||||
|
$this->onceListeners[$event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->onceListeners[$event][] = $listener;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeListener($event, callable $listener)
|
||||||
|
{
|
||||||
|
if ($event === null) {
|
||||||
|
throw new InvalidArgumentException('event name must not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->listeners[$event])) {
|
||||||
|
$index = array_search($listener, $this->listeners[$event], true);
|
||||||
|
if (false !== $index) {
|
||||||
|
unset($this->listeners[$event][$index]);
|
||||||
|
if (count($this->listeners[$event]) === 0) {
|
||||||
|
unset($this->listeners[$event]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->onceListeners[$event])) {
|
||||||
|
$index = array_search($listener, $this->onceListeners[$event], true);
|
||||||
|
if (false !== $index) {
|
||||||
|
unset($this->onceListeners[$event][$index]);
|
||||||
|
if (count($this->onceListeners[$event]) === 0) {
|
||||||
|
unset($this->onceListeners[$event]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAllListeners($event = null)
|
||||||
|
{
|
||||||
|
if ($event !== null) {
|
||||||
|
unset($this->listeners[$event]);
|
||||||
|
} else {
|
||||||
|
$this->listeners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event !== null) {
|
||||||
|
unset($this->onceListeners[$event]);
|
||||||
|
} else {
|
||||||
|
$this->onceListeners = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listeners($event = null): array
|
||||||
|
{
|
||||||
|
if ($event === null) {
|
||||||
|
$events = [];
|
||||||
|
$eventNames = array_unique(
|
||||||
|
array_merge(
|
||||||
|
array_keys($this->listeners),
|
||||||
|
array_keys($this->onceListeners)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
foreach ($eventNames as $eventName) {
|
||||||
|
$events[$eventName] = array_merge(
|
||||||
|
isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [],
|
||||||
|
isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge(
|
||||||
|
isset($this->listeners[$event]) ? $this->listeners[$event] : [],
|
||||||
|
isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function emit($event, array $arguments = [])
|
||||||
|
{
|
||||||
|
if ($event === null) {
|
||||||
|
throw new InvalidArgumentException('event name must not be null');
|
||||||
|
}
|
||||||
|
|
||||||
|
$listeners = [];
|
||||||
|
if (isset($this->listeners[$event])) {
|
||||||
|
$listeners = array_values($this->listeners[$event]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$onceListeners = [];
|
||||||
|
if (isset($this->onceListeners[$event])) {
|
||||||
|
$onceListeners = array_values($this->onceListeners[$event]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(empty($listeners) === false) {
|
||||||
|
foreach ($listeners as $listener) {
|
||||||
|
$listener(...$arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(empty($onceListeners) === false) {
|
||||||
|
unset($this->onceListeners[$event]);
|
||||||
|
foreach ($onceListeners as $listener) {
|
||||||
|
$listener(...$arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vendor/ratchet/rfc6455/.github/workflows/ci.yml
vendored
Normal file
43
src/vendor/ratchet/rfc6455/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
PHPUnit:
|
||||||
|
name: PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }})
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
env:
|
||||||
|
- client
|
||||||
|
- server
|
||||||
|
php:
|
||||||
|
- 7.4
|
||||||
|
- 7.3
|
||||||
|
- 7.2
|
||||||
|
- 7.1
|
||||||
|
- 7.0
|
||||||
|
- 5.6
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
coverage: xdebug
|
||||||
|
- run: docker pull crossbario/autobahn-testsuite
|
||||||
|
- run: composer install
|
||||||
|
|
||||||
|
- run: sh tests/ab/run_ab_tests.sh
|
||||||
|
env:
|
||||||
|
ABTEST: ${{ matrix.env }}
|
||||||
|
SKIP_DEFLATE: _skip_deflate
|
||||||
|
if: ${{ matrix.php <= 5.6 }}
|
||||||
|
|
||||||
|
- run: sh tests/ab/run_ab_tests.sh
|
||||||
|
env:
|
||||||
|
ABTEST: ${{ matrix.env }}
|
||||||
|
if: ${{ matrix.php >= 7.0 }}
|
||||||
|
- run: vendor/bin/phpunit --verbose
|
||||||
19
src/vendor/ratchet/rfc6455/LICENSE
vendored
Normal file
19
src/vendor/ratchet/rfc6455/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2011 Chris Boden
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
13
src/vendor/ratchet/rfc6455/README.md
vendored
Normal file
13
src/vendor/ratchet/rfc6455/README.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# RFC6455 - The WebSocket Protocol
|
||||||
|
|
||||||
|
[](https://github.com/ratchetphp/RFC6455/actions)
|
||||||
|
[](http://socketo.me/reports/rfc-server/index.html)
|
||||||
|
|
||||||
|
This library a protocol handler for the RFC6455 specification.
|
||||||
|
It contains components for both server and client side handshake and messaging protocol negotation.
|
||||||
|
|
||||||
|
Aspects that are left open to interpretation in the specification are also left open in this library.
|
||||||
|
It is up to the implementation to determine how those interpretations are to be dealt with.
|
||||||
|
|
||||||
|
This library is independent, framework agnostic, and does not deal with any I/O.
|
||||||
|
HTTP upgrade negotiation integration points are handled with PSR-7 interfaces.
|
||||||
46
src/vendor/ratchet/rfc6455/composer.json
vendored
Normal file
46
src/vendor/ratchet/rfc6455/composer.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "ratchet/rfc6455",
|
||||||
|
"type": "library",
|
||||||
|
"description": "RFC6455 WebSocket protocol handler",
|
||||||
|
"keywords": ["WebSockets", "websocket", "RFC6455"],
|
||||||
|
"homepage": "http://socketo.me",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Boden"
|
||||||
|
, "email": "cboden@gmail.com"
|
||||||
|
, "role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Bonneau",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/ratchetphp/RFC6455/issues",
|
||||||
|
"chat": "https://gitter.im/reactphp/reactphp"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ratchet\\RFC6455\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.4.2",
|
||||||
|
"guzzlehttp/psr7": "^2 || ^1.7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^5.7",
|
||||||
|
"react/socket": "^1.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"abtest-client": "ABTEST=client && sh tests/ab/run_ab_tests.sh",
|
||||||
|
"abtest-server": "ABTEST=server && sh tests/ab/run_ab_tests.sh",
|
||||||
|
"phpunit": "phpunit --colors=always",
|
||||||
|
"test": [
|
||||||
|
"@abtest-client",
|
||||||
|
"@abtest-server",
|
||||||
|
"@phpunit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/vendor/ratchet/rfc6455/phpunit.xml.dist
vendored
Normal file
27
src/vendor/ratchet/rfc6455/phpunit.xml.dist
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
forceCoversAnnotation="true"
|
||||||
|
mapTestClassNameToCoveredClassName="true"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
colors="true"
|
||||||
|
backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
stopOnError="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="tests">
|
||||||
|
<directory>tests</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory>test/ab</directory>
|
||||||
|
</exclude>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./src/</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
||||||
71
src/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php
vendored
Normal file
71
src/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\RFC6455\Handshake;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
|
use GuzzleHttp\Psr7\Request;
|
||||||
|
|
||||||
|
class ClientNegotiator {
|
||||||
|
/**
|
||||||
|
* @var ResponseVerifier
|
||||||
|
*/
|
||||||
|
private $verifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Psr\Http\Message\RequestInterface
|
||||||
|
*/
|
||||||
|
private $defaultHeader;
|
||||||
|
|
||||||
|
function __construct(PermessageDeflateOptions $perMessageDeflateOptions = null) {
|
||||||
|
$this->verifier = new ResponseVerifier;
|
||||||
|
|
||||||
|
$this->defaultHeader = new Request('GET', '', [
|
||||||
|
'Connection' => 'Upgrade'
|
||||||
|
, 'Upgrade' => 'websocket'
|
||||||
|
, 'Sec-WebSocket-Version' => $this->getVersion()
|
||||||
|
, 'User-Agent' => "Ratchet"
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($perMessageDeflateOptions === null) {
|
||||||
|
$perMessageDeflateOptions = PermessageDeflateOptions::createDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://bugs.php.net/bug.php?id=73373
|
||||||
|
// https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18
|
||||||
|
if ($perMessageDeflateOptions->isEnabled() &&
|
||||||
|
!PermessageDeflateOptions::permessageDeflateSupported()) {
|
||||||
|
trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE);
|
||||||
|
$perMessageDeflateOptions = PermessageDeflateOptions::createDisabled();
|
||||||
|
}
|
||||||
|
if ($perMessageDeflateOptions->isEnabled() && !function_exists('deflate_add')) {
|
||||||
|
trigger_error('permessage-deflate is being disabled because you do not have the zlib extension.', E_USER_NOTICE);
|
||||||
|
$perMessageDeflateOptions = PermessageDeflateOptions::createDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->defaultHeader = $perMessageDeflateOptions->addHeaderToRequest($this->defaultHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateRequest(UriInterface $uri) {
|
||||||
|
return $this->defaultHeader->withUri($uri)
|
||||||
|
->withHeader("Sec-WebSocket-Key", $this->generateKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateResponse(RequestInterface $request, ResponseInterface $response) {
|
||||||
|
return $this->verifier->verifyAll($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateKey() {
|
||||||
|
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/=';
|
||||||
|
$charRange = strlen($chars) - 1;
|
||||||
|
$key = '';
|
||||||
|
for ($i = 0; $i < 16; $i++) {
|
||||||
|
$key .= $chars[mt_rand(0, $charRange)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64_encode($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersion() {
|
||||||
|
return 13;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user