websocket
This commit is contained in:
@@ -176,6 +176,8 @@ module.exports = function (grunt) {
|
||||
'clear_cache.php',
|
||||
'upgrade.php',
|
||||
'extension.php',
|
||||
'websocket.php',
|
||||
'auth_token_check.php',
|
||||
'index.php',
|
||||
'LICENSE.txt',
|
||||
'.htaccess',
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Loaders;
|
||||
|
||||
class WebSocketSubmission extends Base
|
||||
{
|
||||
public function load()
|
||||
{
|
||||
return new \Espo\Core\WebSocket\Submission(
|
||||
$this->getContainer()->get('config')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\WebSocket;
|
||||
|
||||
use Ratchet\ConnectionInterface;
|
||||
use Ratchet\Wamp\WampServerInterface;
|
||||
|
||||
class Pusher implements WampServerInterface
|
||||
{
|
||||
private $categoryList;
|
||||
|
||||
protected $connectionIdUserIdMap = [];
|
||||
|
||||
protected $userIdConnectionIdListMap = [];
|
||||
|
||||
protected $connectionIdTopicIdListMap = [];
|
||||
|
||||
protected $connections = [];
|
||||
|
||||
public function __construct(array $categoryList = [])
|
||||
{
|
||||
$this->categoryList = $categoryList;
|
||||
}
|
||||
|
||||
public function onSubscribe(ConnectionInterface $connection, $topic)
|
||||
{
|
||||
$topicId = $topic->getId();
|
||||
if (!$topicId) return;
|
||||
|
||||
if (!$this->isCategoryAllowed($topicId)) return;
|
||||
|
||||
$connectionId = $connection->resourceId;
|
||||
|
||||
$userId = $this->getUserIdByConnection($connection);
|
||||
if (!$userId) return;
|
||||
|
||||
if (!isset($this->connectionIdTopicIdListMap[$connectionId])) $this->connectionIdTopicIdListMap[$connectionId] = [];
|
||||
|
||||
if (!in_array($topicId, $this->connectionIdTopicIdListMap[$connectionId])) {
|
||||
echo "add topic {$topicId} for user {$userId}\n";
|
||||
$this->connectionIdTopicIdListMap[$connectionId][] = $topicId;
|
||||
}
|
||||
}
|
||||
|
||||
public function onUnSubscribe(ConnectionInterface $connection, $topic)
|
||||
{
|
||||
$topicId = $topic->getId();
|
||||
if (!$topicId) return;
|
||||
|
||||
if (!$this->isCategoryAllowed($topicId)) return;
|
||||
|
||||
$connectionId = $connection->resourceId;
|
||||
|
||||
$userId = $this->getUserIdByConnection($connection);
|
||||
if (!$userId) return;
|
||||
|
||||
if (isset($this->connectionIdTopicIdListMap[$connectionId])) {
|
||||
$index = array_search($topicId, $this->connectionIdTopicIdListMap[$connectionId]);
|
||||
if ($index !== false) {
|
||||
echo "remove topic {$topicId} for user {$userId}\n";
|
||||
$this->connectionIdTopicIdListMap[$connectionId] = array_splice($this->connectionIdTopicIdListMap[$connectionId], $index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isCategoryAllowed($category)
|
||||
{
|
||||
return in_array($category, $this->categoryList);
|
||||
}
|
||||
|
||||
protected function getConnectionIdListByUserId($userId)
|
||||
{
|
||||
if (!isset($this->userIdConnectionIdListMap[$userId])) return [];
|
||||
return $this->userIdConnectionIdListMap[$userId];
|
||||
}
|
||||
|
||||
protected function getUserIdByConnection(ConnectionInterface $connection)
|
||||
{
|
||||
if (!isset($this->connectionIdUserIdMap[$connection->resourceId])) return;
|
||||
return $this->connectionIdUserIdMap[$connection->resourceId];
|
||||
}
|
||||
|
||||
protected function subscribeUser(ConnectionInterface $connection, $userId)
|
||||
{
|
||||
$resourceId = $connection->resourceId;
|
||||
|
||||
$this->connectionIdUserIdMap[$resourceId] = $userId;
|
||||
|
||||
if (!isset($this->userIdConnectionIdListMap[$userId])) $this->userIdConnectionIdListMap[$userId] = [];
|
||||
|
||||
if (!in_array($resourceId, $this->userIdConnectionIdListMap[$userId])) {
|
||||
$this->userIdConnectionIdListMap[$userId][] = $resourceId;
|
||||
}
|
||||
|
||||
$this->connections[$resourceId] = $connection;
|
||||
|
||||
echo "{$userId} subscribed\n";
|
||||
}
|
||||
|
||||
protected function unsubscribeUser(ConnectionInterface $connection, $userId)
|
||||
{
|
||||
$resourceId = $connection->resourceId;
|
||||
|
||||
unset($this->connectionIdUserIdMap[$resourceId]);
|
||||
|
||||
if (isset($this->userIdConnectionIdListMap[$userId])) {
|
||||
$index = array_search($resourceId, $this->userIdConnectionIdListMap[$userId]);
|
||||
if ($index !== false) {
|
||||
$this->userIdConnectionIdListMap[$userId] = array_splice($this->userIdConnectionIdListMap[$userId], $index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
echo "{$userId} unsubscribed\n";
|
||||
}
|
||||
|
||||
public function onOpen(ConnectionInterface $connection)
|
||||
{
|
||||
echo "onOpen {$connection->resourceId}\n";
|
||||
|
||||
$query = $connection->httpRequest->getUri()->getQuery();
|
||||
$params = \GuzzleHttp\Psr7\parse_query($query ?: '');
|
||||
if (empty($params['userId']) || empty($params['authToken'])) {
|
||||
$this->closeConnection($connection);
|
||||
return;
|
||||
}
|
||||
|
||||
$authToken = preg_replace('/[^a-zA-Z0-9]+/', '', $params['authToken']);
|
||||
$userId = $params['userId'];
|
||||
|
||||
$result = shell_exec("php auth_token_check.php " . $authToken);
|
||||
if (empty($result)) {
|
||||
$this->closeConnection($connection);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result !== $userId) {
|
||||
$this->closeConnection($connection);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->subscribeUser($connection, $userId);
|
||||
}
|
||||
|
||||
protected function closeConnection(ConnectionInterface $connection)
|
||||
{
|
||||
$userId = $this->getUserIdByConnection($connection);
|
||||
if ($userId) {
|
||||
$this->unsubscribeUser($connection, $userId);
|
||||
}
|
||||
|
||||
$connection->close();
|
||||
}
|
||||
|
||||
public function onClose(ConnectionInterface $connection)
|
||||
{
|
||||
echo "onClose {$connection->resourceId}\n";
|
||||
|
||||
$userId = $this->getUserIdByConnection($connection);
|
||||
|
||||
if ($userId) {
|
||||
$this->unsubscribeUser($connection, $userId);
|
||||
}
|
||||
|
||||
unset($this->connections[$connection->resourceId]);
|
||||
}
|
||||
|
||||
public function onCall(ConnectionInterface $connection, $id, $topic, array $params)
|
||||
{
|
||||
$connection->callError($id, $topic, 'You are not allowed to make calls')->close();
|
||||
}
|
||||
|
||||
public function onPublish(ConnectionInterface $connection, $topic, $event, array $exclude, array $eligible)
|
||||
{
|
||||
$topicId = $topic->getId();
|
||||
$connection->close();
|
||||
}
|
||||
|
||||
public function onError(ConnectionInterface $connection, \Exception $e)
|
||||
{
|
||||
}
|
||||
|
||||
public function onMessageReceive($dataString)
|
||||
{
|
||||
$data = json_decode($dataString);
|
||||
|
||||
if (!property_exists($data, 'category')) return;
|
||||
if (!property_exists($data, 'userId')) return;
|
||||
|
||||
$userId = $data->userId;
|
||||
$category = $data->category;
|
||||
|
||||
if (!$userId || !$category) return;
|
||||
|
||||
if (!in_array($category, $this->categoryList)) return;
|
||||
|
||||
foreach ($this->getConnectionIdListByUserId($userId) as $connectionId) {
|
||||
if (!isset($this->connections[$connectionId])) continue;
|
||||
if (!isset($this->connectionIdTopicIdListMap[$connectionId])) continue;
|
||||
|
||||
$connection = $this->connections[$connectionId];
|
||||
|
||||
if (in_array($category, $this->connectionIdTopicIdListMap[$connectionId])) {
|
||||
echo "send {$category} for connection {$connectionId}\n";
|
||||
$connection->event($category, $data);
|
||||
}
|
||||
}
|
||||
|
||||
echo "onMessage {$category} for {$userId}\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\WebSocket;
|
||||
|
||||
class Submission
|
||||
{
|
||||
protected $config;
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Config $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function submit(string $category, ?string $userId = null, $data = null)
|
||||
{
|
||||
if (!$data) $data = (object) [];
|
||||
|
||||
$dsn = $this->config->get('webSocketSubmissionDsn', 'tcp://localhost:5555');
|
||||
|
||||
$data->userId = $userId;
|
||||
$data->category = $category;
|
||||
|
||||
try {
|
||||
$context = new \ZMQContext();
|
||||
$socket = $context->getSocket(\ZMQ::SOCKET_PUSH, 'my pusher');
|
||||
$socket->connect($dsn);
|
||||
|
||||
$socket->send(json_encode($data));
|
||||
} catch (\Throwable $e) {
|
||||
$GLOBALS['log']->error("WebSocketSubmission: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,5 +177,6 @@ return [
|
||||
'noteDeleteThresholdPeriod' => '1 month',
|
||||
'noteEditThresholdPeriod' => '7 days',
|
||||
'emailForceUseExternalClient' => false,
|
||||
'useWebSocket' => false,
|
||||
'isInstalled' => false
|
||||
];
|
||||
|
||||
@@ -200,6 +200,7 @@ return [
|
||||
'cronDisabled',
|
||||
'maintenanceMode',
|
||||
'siteUrl',
|
||||
'useWebSocket',
|
||||
],
|
||||
'userItems' => [
|
||||
'outboundEmailFromAddress',
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Hooks\Notification;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class WebSocketSubmit extends \Espo\Core\Hooks\Base
|
||||
{
|
||||
public static $order = 20;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('webSocketSubmission');
|
||||
}
|
||||
|
||||
public function afterSave(Entity $entity, array $options = [])
|
||||
{
|
||||
if (!$this->getConfig()->get('useWebSocket')) return;
|
||||
if (!$entity->isNew()) return;
|
||||
$userId = $entity->get('userId');
|
||||
if (!$userId) return;
|
||||
|
||||
$this->getInjection('webSocketSubmission')->submit('newNotification', $userId);
|
||||
}
|
||||
}
|
||||
@@ -116,10 +116,14 @@ class EmailReminder
|
||||
$user = $this->getEntityManager()->getEntity('User', $reminder->get('userId'));
|
||||
$entity = $this->getEntityManager()->getEntity($reminder->get('entityType'), $reminder->get('entityId'));
|
||||
|
||||
if (!$user || !$entity) return;
|
||||
$emailAddress = $user->get('emailAddress');
|
||||
if (!$emailAddress) return;
|
||||
|
||||
if (empty($user) || empty($emailAddress) || empty($entity)) {
|
||||
return;
|
||||
if ($entity->hasLinkMultipleField('users')) {
|
||||
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
|
||||
$status = $entity->getLinkMultipleColumn('users', 'status', $user->id);
|
||||
if ($status === 'Declined') return;
|
||||
}
|
||||
|
||||
$email = $this->getEntityManager()->getEntity('Email');
|
||||
@@ -128,9 +132,9 @@ class EmailReminder
|
||||
$subjectTpl = $this->getTemplateFileManager()->getTemplate('reminder', 'subject', $entity->getEntityType(), 'Crm');
|
||||
$bodyTpl = $this->getTemplateFileManager()->getTemplate('reminder', 'body', $entity->getEntityType(), 'Crm');
|
||||
|
||||
$subjectTpl = str_replace(array("\n", "\r"), '', $subjectTpl);
|
||||
$subjectTpl = str_replace(["\n", "\r"], '', $subjectTpl);
|
||||
|
||||
$data = array();
|
||||
$data = [];
|
||||
|
||||
$siteUrl = rtrim($this->getConfig()->get('siteUrl'), '/');
|
||||
$recordUrl = $siteUrl . '/#' . $entity->getEntityType() . '/view/' . $entity->id;
|
||||
@@ -164,4 +168,3 @@ class EmailReminder
|
||||
$emailSender->send($email);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Crm\Jobs;
|
||||
|
||||
use \Espo\Core\Exceptions;
|
||||
|
||||
class SubmitPopupReminders extends \Espo\Core\Jobs\Base
|
||||
{
|
||||
const REMINDER_PAST_HOURS = 24;
|
||||
|
||||
public function run()
|
||||
{
|
||||
if (!$this->getConfig()->get('useWebSocket')) return;
|
||||
|
||||
$dt = new \DateTime();
|
||||
|
||||
$now = $dt->format('Y-m-d H:i:s');
|
||||
|
||||
$pastHours = $this->getConfig()->get('reminderPastHours', self::REMINDER_PAST_HOURS);
|
||||
$nowShifted = $dt->modify('-' . $pastHours . ' hours')->format('Y-m-d H:i:s');
|
||||
|
||||
$reminderList = $this->getEntityManager()->getRepository('Reminder')->where([
|
||||
'type' => 'Popup',
|
||||
'remindAt<=' => $now,
|
||||
'startAt>' => $nowShifted,
|
||||
])->find();
|
||||
|
||||
$submitData = [];
|
||||
|
||||
foreach ($reminderList as $reminder) {
|
||||
$userId = $reminder->get('userId');
|
||||
$entityType = $reminder->get('entityType');
|
||||
$entityId = $reminder->get('entityId');
|
||||
|
||||
if (!$userId || !$entityType || !$entityId) {
|
||||
$this->deleteReminder($reminder);
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntity($entityType, $entityId);
|
||||
|
||||
if (!$entity) {
|
||||
$this->deleteReminder($reminder);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entity->hasLinkMultipleField('users')) {
|
||||
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
|
||||
$status = $entity->getLinkMultipleColumn('users', 'status', $userId);
|
||||
if ($status === 'Declined') {
|
||||
$this->deleteReminder($reminder);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$dateAttribute = 'dateStart';
|
||||
if ($entityType === 'Task') {
|
||||
$dateAttribute = 'dateEnd';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $reminder->id,
|
||||
'data' => [
|
||||
'id' => $entity->id,
|
||||
'entityType' => $entityType,
|
||||
$dateAttribute => $entity->get($dateAttribute),
|
||||
'name' => $entity->get('name'),
|
||||
],
|
||||
];
|
||||
|
||||
if (!array_key_exists($userId, $submitData)) {
|
||||
$submitData[$userId] = [];
|
||||
}
|
||||
|
||||
$submitData[$userId][] = $data;
|
||||
}
|
||||
|
||||
foreach ($submitData as $userId => $list) {
|
||||
try {
|
||||
$this->getContainer()->get('webSocketSubmission')->submit('popupNotifications.event', $userId, (object) [
|
||||
'list' => $list
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$GLOBALS['log']->error('Job SubmitPopupReminders: [' . $e->getCode() . '] ' .$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function deleteReminder($reminder)
|
||||
{
|
||||
$this->getEntityManager()->getRepository('Reminder')->deleteFromDb($reminder->id);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
"event": {
|
||||
"url": "Activities/action/popupNotifications",
|
||||
"interval": 15,
|
||||
"useWebSocket": true,
|
||||
"portalDisabled": true,
|
||||
"view": "crm:views/meeting/popup-notification"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"categories": {
|
||||
"popupNotifications.event": {}
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,12 @@
|
||||
"jobSchedulingMap": {
|
||||
"ProcessMassEmail": "15 * * * *",
|
||||
"ControlKnowledgeBaseArticleStatus": "10 1 * * *"
|
||||
},
|
||||
"jobs": {
|
||||
"SubmitPopupReminders": {
|
||||
"name": "Submit Popup Reminders",
|
||||
"isSystem": true,
|
||||
"scheduling": "* * * * *"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1475,7 +1475,7 @@ class Activities extends \Espo\Core\Services\Base
|
||||
$sth->execute();
|
||||
$rowList = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$result = array();
|
||||
$resultList = [];
|
||||
foreach ($rowList as $row) {
|
||||
$reminderId = $row['id'];
|
||||
$entityType = $row['entityType'];
|
||||
@@ -1486,7 +1486,7 @@ class Activities extends \Espo\Core\Services\Base
|
||||
|
||||
if ($entity) {
|
||||
if ($entity->hasLinkMultipleField('users')) {
|
||||
$entity->loadLinkMultipleField('users', array('status' => 'acceptanceStatus'));
|
||||
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
|
||||
$status = $entity->getLinkMultipleColumn('users', 'status', $userId);
|
||||
if ($status === 'Declined') {
|
||||
$this->removeReminder($reminderId);
|
||||
@@ -1499,22 +1499,22 @@ class Activities extends \Espo\Core\Services\Base
|
||||
$dateAttribute = 'dateEnd';
|
||||
}
|
||||
|
||||
$data = array(
|
||||
$data = [
|
||||
'id' => $entity->id,
|
||||
'entityType' => $entityType,
|
||||
$dateAttribute => $entity->get($dateAttribute),
|
||||
'name' => $entity->get('name')
|
||||
);
|
||||
];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
$result[] = array(
|
||||
$resultList[] = [
|
||||
'id' => $reminderId,
|
||||
'data' => $data
|
||||
);
|
||||
];
|
||||
|
||||
}
|
||||
return $result;
|
||||
return $resultList;
|
||||
}
|
||||
|
||||
public function getUpcomingActivities($userId, $params = array(), $entityTypeList = null, $futureDays = null)
|
||||
|
||||
@@ -116,7 +116,8 @@
|
||||
"daemonMaxProcessNumber": "Daemon Max Process Number",
|
||||
"daemonProcessTimeout": "Daemon Process Timeout",
|
||||
"cronDisabled": "Disable Cron",
|
||||
"maintenanceMode": "Maintenance Mode"
|
||||
"maintenanceMode": "Maintenance Mode",
|
||||
"useWebSocket": "Use WebSocket"
|
||||
},
|
||||
"options": {
|
||||
"weekStart": {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"rows": [
|
||||
[{"name": "useCache"}, {"name": "siteUrl"}],
|
||||
[{"name": "b2cMode"}, {"name": "aclStrictMode"}],
|
||||
[{"name": "maintenanceMode"}, {"name": "cronDisabled"}]
|
||||
[{"name": "maintenanceMode"}, {"name": "cronDisabled"}],
|
||||
[{"name": "useWebSocket"}, false]
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"categories": {
|
||||
"newNotification": {}
|
||||
}
|
||||
}
|
||||
@@ -544,6 +544,9 @@
|
||||
"maintenanceMode": {
|
||||
"type": "bool",
|
||||
"tooltip": true
|
||||
},
|
||||
"useWebSocket": {
|
||||
"type": "bool"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ class Notification extends \Espo\Services\Record
|
||||
{
|
||||
protected $actionHistoryDisabled = true;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->addDependency('container');
|
||||
}
|
||||
|
||||
public function notifyAboutMentionInPost($userId, $noteId)
|
||||
{
|
||||
$notification = $this->getEntityManager()->getEntity('Notification');
|
||||
@@ -84,6 +90,12 @@ class Notification extends \Espo\Services\Record
|
||||
|
||||
$sql .= implode(", ", $arr);
|
||||
$pdo->query($sql);
|
||||
|
||||
if ($this->getConfig()->get('useWebSocket')) {
|
||||
foreach ($userIdList as $useId) {
|
||||
$this->getInjection('container')->get('webSocketSubmission')->submit('newNotification', $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function checkUserNoteAccess(\Espo\Entities\User $user, \Espo\Entities\Note $note)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
ob_start();
|
||||
|
||||
if (substr(php_sapi_name(), 0, 3) != 'cli') exit;
|
||||
|
||||
$token = isset($_SERVER['argv'][1]) ? trim($_SERVER['argv'][1]) : null;
|
||||
if (empty($token)) exit;
|
||||
|
||||
include "bootstrap.php";
|
||||
|
||||
$app = new \Espo\Core\Application();
|
||||
$entityManager = $app->getContainer()->get('entityManager');
|
||||
|
||||
$authToken = $entityManager->getRepository('AuthToken')->where([
|
||||
'token' => $token,
|
||||
'isActive' => true,
|
||||
])->findOne();
|
||||
|
||||
if (!$authToken) exit;
|
||||
if (!$authToken->get('userId')) exit;
|
||||
|
||||
$userId = $authToken->get('userId');
|
||||
|
||||
$user = $entityManager->getEntity('User', $userId);
|
||||
if (!$user) exit;
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
echo $user->id;
|
||||
|
||||
exit;
|
||||
@@ -0,0 +1,824 @@
|
||||
/** @license MIT License (c) 2011,2012 Copyright Tavendo GmbH. */
|
||||
|
||||
/**
|
||||
* AutobahnJS - http://autobahn.ws
|
||||
*
|
||||
* A lightweight implementation of
|
||||
*
|
||||
* WAMP (The WebSocket Application Messaging Protocol) - http://wamp.ws
|
||||
*
|
||||
* Provides asynchronous RPC/PubSub over WebSocket.
|
||||
*
|
||||
* Copyright 2011, 2012 Tavendo GmbH. Licensed under the MIT License.
|
||||
* See license text at http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/** @define {string} */
|
||||
var AUTOBAHNJS_VERSION = '?.?.?';
|
||||
|
||||
/** @define {boolean} */
|
||||
var AUTOBAHNJS_DEBUG = true;
|
||||
|
||||
|
||||
|
||||
var ab = window.ab = {};
|
||||
|
||||
ab._version = AUTOBAHNJS_VERSION;
|
||||
|
||||
/**
|
||||
* Fallbacks for browsers lacking
|
||||
*
|
||||
* Array.prototype.indexOf
|
||||
* Array.prototype.forEach
|
||||
*
|
||||
* most notably MSIE8.
|
||||
*
|
||||
* Source:
|
||||
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
|
||||
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
|
||||
*/
|
||||
(function () {
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
|
||||
"use strict";
|
||||
if (this === null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
var t = new Object(this);
|
||||
var len = t.length >>> 0;
|
||||
if (len === 0) {
|
||||
return -1;
|
||||
}
|
||||
var n = 0;
|
||||
if (arguments.length > 0) {
|
||||
n = Number(arguments[1]);
|
||||
if (n !== n) { // shortcut for verifying if it's NaN
|
||||
n = 0;
|
||||
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
|
||||
n = (n > 0 || -1) * Math.floor(Math.abs(n));
|
||||
}
|
||||
}
|
||||
if (n >= len) {
|
||||
return -1;
|
||||
}
|
||||
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
|
||||
for (; k < len; k++) {
|
||||
if (k in t && t[k] === searchElement) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
if (!Array.prototype.forEach) {
|
||||
|
||||
Array.prototype.forEach = function (callback, thisArg) {
|
||||
|
||||
var T, k;
|
||||
|
||||
if (this === null) {
|
||||
throw new TypeError(" this is null or not defined");
|
||||
}
|
||||
|
||||
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
|
||||
var O = new Object(this);
|
||||
|
||||
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
|
||||
// 3. Let len be ToUint32(lenValue).
|
||||
var len = O.length >>> 0; // Hack to convert O.length to a UInt32
|
||||
|
||||
// 4. If IsCallable(callback) is false, throw a TypeError exception.
|
||||
// See: http://es5.github.com/#x9.11
|
||||
if ({}.toString.call(callback) !== "[object Function]") {
|
||||
throw new TypeError(callback + " is not a function");
|
||||
}
|
||||
|
||||
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||
if (thisArg) {
|
||||
T = thisArg;
|
||||
}
|
||||
|
||||
// 6. Let k be 0
|
||||
k = 0;
|
||||
|
||||
// 7. Repeat, while k < len
|
||||
while (k < len) {
|
||||
|
||||
var kValue;
|
||||
|
||||
// a. Let Pk be ToString(k).
|
||||
// This is implicit for LHS operands of the in operator
|
||||
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
|
||||
// This step can be combined with c
|
||||
// c. If kPresent is true, then
|
||||
if (k in O) {
|
||||
|
||||
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
|
||||
kValue = O[k];
|
||||
|
||||
// ii. Call the Call internal method of callback with T as the this value and
|
||||
// argument list containing kValue, k, and O.
|
||||
callback.call(T, kValue, k, O);
|
||||
}
|
||||
// d. Increase k by 1.
|
||||
k++;
|
||||
}
|
||||
// 8. return undefined
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// Helper to slice out browser / version from userAgent
|
||||
ab._sliceUserAgent = function (str, delim, delim2) {
|
||||
var ver = [];
|
||||
var ua = navigator.userAgent;
|
||||
var i = ua.indexOf(str);
|
||||
var j = ua.indexOf(delim, i);
|
||||
if (j < 0) {
|
||||
j = ua.length;
|
||||
}
|
||||
var agent = ua.slice(i, j).split(delim2);
|
||||
var v = agent[1].split('.');
|
||||
for (var k = 0; k < v.length; ++k) {
|
||||
ver.push(parseInt(v[k], 10));
|
||||
}
|
||||
return {name: agent[0], version: ver};
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect browser and browser version.
|
||||
*/
|
||||
ab.getBrowser = function () {
|
||||
|
||||
var ua = navigator.userAgent;
|
||||
if (ua.indexOf("Chrome") > -1) {
|
||||
return ab._sliceUserAgent("Chrome", " ", "/");
|
||||
} else if (ua.indexOf("Safari") > -1) {
|
||||
return ab._sliceUserAgent("Safari", " ", "/");
|
||||
} else if (ua.indexOf("Firefox") > -1) {
|
||||
return ab._sliceUserAgent("Firefox", " ", "/");
|
||||
} else if (ua.indexOf("MSIE") > -1) {
|
||||
return ab._sliceUserAgent("MSIE", ";", " ");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Logging message for unsupported browser.
|
||||
ab.browserNotSupportedMessage = "Browser does not support WebSockets (RFC6455)";
|
||||
|
||||
|
||||
ab._idchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
ab._idlen = 16;
|
||||
ab._subprotocol = "wamp";
|
||||
|
||||
ab._newid = function () {
|
||||
var id = "";
|
||||
for (var i = 0; i < ab._idlen; i += 1) {
|
||||
id += ab._idchars.charAt(Math.floor(Math.random() * ab._idchars.length));
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
ab.log = function (o) {
|
||||
if (window.console && console.log) {
|
||||
//console.log.apply(console, !!arguments.length ? arguments : [this]);
|
||||
if (arguments.length > 1) {
|
||||
console.group("Log Item");
|
||||
for (var i = 0; i < arguments.length; i += 1) {
|
||||
console.log(arguments[i]);
|
||||
}
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log(arguments[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ab._debugrpc = false;
|
||||
ab._debugpubsub = false;
|
||||
ab._debugws = false;
|
||||
|
||||
ab.debug = function (debugWamp, debugWs) {
|
||||
if ("console" in window) {
|
||||
ab._debugrpc = debugWamp;
|
||||
ab._debugpubsub = debugWamp;
|
||||
ab._debugws = debugWs;
|
||||
} else {
|
||||
throw "browser does not support console object";
|
||||
}
|
||||
};
|
||||
|
||||
ab.version = function () {
|
||||
return ab._version;
|
||||
};
|
||||
|
||||
ab.PrefixMap = function () {
|
||||
|
||||
var self = this;
|
||||
self._index = {};
|
||||
self._rindex = {};
|
||||
};
|
||||
|
||||
ab.PrefixMap.prototype.get = function (prefix) {
|
||||
|
||||
var self = this;
|
||||
return self._index[prefix];
|
||||
};
|
||||
|
||||
ab.PrefixMap.prototype.set = function (prefix, uri) {
|
||||
|
||||
var self = this;
|
||||
self._index[prefix] = uri;
|
||||
self._rindex[uri] = prefix;
|
||||
};
|
||||
|
||||
ab.PrefixMap.prototype.setDefault = function (uri) {
|
||||
|
||||
var self = this;
|
||||
self._index[""] = uri;
|
||||
self._rindex[uri] = "";
|
||||
};
|
||||
|
||||
ab.PrefixMap.prototype.remove = function (prefix) {
|
||||
|
||||
var self = this;
|
||||
var uri = self._index[prefix];
|
||||
if (uri) {
|
||||
delete self._index[prefix];
|
||||
delete self._rindex[uri];
|
||||
}
|
||||
};
|
||||
|
||||
ab.PrefixMap.prototype.resolve = function (curie, pass) {
|
||||
|
||||
var self = this;
|
||||
|
||||
// skip if not a CURIE
|
||||
var i = curie.indexOf(":");
|
||||
if (i >= 0) {
|
||||
var prefix = curie.substring(0, i);
|
||||
if (self._index[prefix]) {
|
||||
return self._index[prefix] + curie.substring(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// either pass-through or null
|
||||
if (pass == true) {
|
||||
return curie;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
ab.PrefixMap.prototype.shrink = function (uri, pass) {
|
||||
|
||||
var self = this;
|
||||
|
||||
// skip if already a CURIE
|
||||
var i = uri.indexOf(":");
|
||||
if (i == -1) {
|
||||
for (var i = uri.length; i > 0; i -= 1) {
|
||||
var u = uri.substring(0, i);
|
||||
var p = self._rindex[u];
|
||||
if (p) {
|
||||
return p + ":" + uri.substring(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// either pass-through or null
|
||||
if (pass == true) {
|
||||
return uri;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ab._MESSAGE_TYPEID_WELCOME = 0;
|
||||
ab._MESSAGE_TYPEID_PREFIX = 1;
|
||||
ab._MESSAGE_TYPEID_CALL = 2;
|
||||
ab._MESSAGE_TYPEID_CALL_RESULT = 3;
|
||||
ab._MESSAGE_TYPEID_CALL_ERROR = 4;
|
||||
ab._MESSAGE_TYPEID_SUBSCRIBE = 5;
|
||||
ab._MESSAGE_TYPEID_UNSUBSCRIBE = 6;
|
||||
ab._MESSAGE_TYPEID_PUBLISH = 7;
|
||||
ab._MESSAGE_TYPEID_EVENT = 8;
|
||||
|
||||
|
||||
ab.CONNECTION_CLOSED = 0;
|
||||
ab.CONNECTION_LOST = 1;
|
||||
ab.CONNECTION_UNREACHABLE = 2;
|
||||
ab.CONNECTION_UNSUPPORTED = 3;
|
||||
|
||||
ab.Session = function (wsuri, onopen, onclose, options) {
|
||||
|
||||
var self = this;
|
||||
|
||||
self._wsuri = wsuri;
|
||||
self._options = options;
|
||||
self._websocket_onopen = onopen;
|
||||
self._websocket_onclose = onclose;
|
||||
self._websocket = null;
|
||||
self._websocket_connected = false;
|
||||
self._session_id = null;
|
||||
self._calls = {};
|
||||
self._subscriptions = {};
|
||||
self._prefixes = new ab.PrefixMap();
|
||||
|
||||
self._txcnt = 0;
|
||||
self._rxcnt = 0;
|
||||
|
||||
if ("WebSocket" in window) {
|
||||
// Chrome, MSIE, newer Firefox
|
||||
self._websocket = new WebSocket(self._wsuri, [ab._subprotocol]);
|
||||
} else if ("MozWebSocket" in window) {
|
||||
// older versions of Firefox prefix the WebSocket object
|
||||
self._websocket = new MozWebSocket(self._wsuri, [ab._subprotocol]);
|
||||
} else {
|
||||
if (onclose !== undefined) {
|
||||
onclose(ab.CONNECTION_UNSUPPORTED);
|
||||
return;
|
||||
} else {
|
||||
throw ab.browserNotSupportedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
self._websocket.onmessage = function (e)
|
||||
{
|
||||
if (ab._debugws) {
|
||||
self._rxcnt += 1;
|
||||
console.group("WS Receive");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(self._rxcnt);
|
||||
console.log(e.data);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
var o = JSON.parse(e.data);
|
||||
if (o[1] in self._calls)
|
||||
{
|
||||
if (o[0] === ab._MESSAGE_TYPEID_CALL_RESULT) {
|
||||
|
||||
var dr = self._calls[o[1]];
|
||||
var r = o[2];
|
||||
|
||||
if (ab._debugrpc && dr._ab_callobj !== undefined) {
|
||||
console.group("WAMP Call", dr._ab_callobj[2]);
|
||||
console.timeEnd(dr._ab_tid);
|
||||
console.group("Arguments");
|
||||
for (var i = 3; i < dr._ab_callobj.length; i += 1) {
|
||||
var arg = dr._ab_callobj[i];
|
||||
if (arg !== undefined) {
|
||||
console.log(arg);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
console.group("Result");
|
||||
console.log(r);
|
||||
console.groupEnd();
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
dr.resolve(r);
|
||||
}
|
||||
else if (o[0] === ab._MESSAGE_TYPEID_CALL_ERROR) {
|
||||
|
||||
var de = self._calls[o[1]];
|
||||
var uri = o[2];
|
||||
var desc = o[3];
|
||||
var detail = o[4];
|
||||
|
||||
if (ab._debugrpc && de._ab_callobj !== undefined) {
|
||||
console.group("WAMP Call", de._ab_callobj[2]);
|
||||
console.timeEnd(de._ab_tid);
|
||||
console.group("Arguments");
|
||||
for (var j = 3; j < de._ab_callobj.length; j += 1) {
|
||||
var arg2 = de._ab_callobj[j];
|
||||
if (arg2 !== undefined) {
|
||||
console.log(arg2);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
console.group("Error");
|
||||
console.log(uri);
|
||||
console.log(desc);
|
||||
if (detail !== undefined) {
|
||||
console.log(detail);
|
||||
}
|
||||
console.groupEnd();
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
if (detail !== undefined) {
|
||||
de.reject(uri, desc, detail);
|
||||
} else {
|
||||
de.reject(uri, desc);
|
||||
}
|
||||
}
|
||||
delete self._calls[o[1]];
|
||||
}
|
||||
else if (o[0] === ab._MESSAGE_TYPEID_EVENT)
|
||||
{
|
||||
var subid = self._prefixes.resolve(o[1], true);
|
||||
if (subid in self._subscriptions) {
|
||||
|
||||
var uri2 = o[1];
|
||||
var val = o[2];
|
||||
|
||||
if (ab._debugpubsub) {
|
||||
console.group("WAMP Event");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(uri2);
|
||||
console.log(val);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
self._subscriptions[subid].forEach(function (callback) {
|
||||
|
||||
callback(uri2, val);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// ignore unsolicited event!
|
||||
}
|
||||
}
|
||||
else if (o[0] === ab._MESSAGE_TYPEID_WELCOME)
|
||||
{
|
||||
if (self._session_id === null) {
|
||||
self._session_id = o[1];
|
||||
self._wamp_version = o[2];
|
||||
self._server = o[3];
|
||||
|
||||
if (ab._debugrpc || ab._debugpubsub) {
|
||||
console.group("WAMP Welcome");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(self._wamp_version);
|
||||
console.log(self._server);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// only now that we have received the initial server-to-client
|
||||
// welcome message, fire application onopen() hook
|
||||
if (self._websocket_onopen !== null) {
|
||||
self._websocket_onopen(self._session_id, self._wamp_version, self._server);
|
||||
}
|
||||
} else {
|
||||
throw "protocol error (welcome message received more than once)";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self._websocket.onopen = function (e)
|
||||
{
|
||||
// check if we can speak WAMP!
|
||||
if (self._websocket.protocol !== ab._subprotocol) {
|
||||
|
||||
if (typeof self._websocket.protocol === 'undefined') {
|
||||
// i.e. Safari does subprotocol negotiation (broken), but then
|
||||
// does NOT set the protocol attribute of the websocket object (broken)
|
||||
//
|
||||
if (ab._debugws) {
|
||||
console.group("WS Warning");
|
||||
console.info(self._wsuri);
|
||||
console.log("WebSocket object has no protocol attribute: WAMP subprotocol check skipped!");
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
else if (self._options && self._options.skipSubprotocolCheck) {
|
||||
// WAMP subprotocol check disabled by session option
|
||||
//
|
||||
if (ab._debugws) {
|
||||
console.group("WS Warning");
|
||||
console.info(self._wsuri);
|
||||
console.log("Server does not speak WAMP, but subprotocol check disabled by option!");
|
||||
console.log(self._websocket.protocol);
|
||||
console.groupEnd();
|
||||
}
|
||||
} else {
|
||||
// we only speak WAMP .. if the server denied us this, we bail out.
|
||||
//
|
||||
self._websocket.close(1000, "server does not speak WAMP");
|
||||
throw "server does not speak WAMP (but '" + self._websocket.protocol + "' !)";
|
||||
}
|
||||
}
|
||||
if (ab._debugws) {
|
||||
console.group("WAMP Connect");
|
||||
console.info(self._wsuri);
|
||||
console.log(self._websocket.protocol);
|
||||
console.groupEnd();
|
||||
}
|
||||
self._websocket_connected = true;
|
||||
};
|
||||
|
||||
self._websocket.onerror = function (e)
|
||||
{
|
||||
// FF fires this upon unclean closes
|
||||
// Chrome does not fire this
|
||||
};
|
||||
|
||||
self._websocket.onclose = function (e)
|
||||
{
|
||||
if (ab._debugws) {
|
||||
if (self._websocket_connected) {
|
||||
console.log("Autobahn connection to " + self._wsuri + " lost (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ").");
|
||||
} else {
|
||||
console.log("Autobahn could not connect to " + self._wsuri + " (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ").");
|
||||
}
|
||||
}
|
||||
|
||||
// fire app callback
|
||||
if (self._websocket_onclose !== undefined) {
|
||||
if (self._websocket_connected) {
|
||||
if (e.wasClean) {
|
||||
// connection was closed cleanly (closing HS was performed)
|
||||
self._websocket_onclose(ab.CONNECTION_CLOSED);
|
||||
} else {
|
||||
// connection was closed uncleanly (lost without closing HS)
|
||||
self._websocket_onclose(ab.CONNECTION_LOST);
|
||||
}
|
||||
} else {
|
||||
// connection could not be established in the first place
|
||||
self._websocket_onclose(ab.CONNECTION_UNREACHABLE);
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup - reconnect requires a new session object!
|
||||
self._websocket_connected = false;
|
||||
self._wsuri = null;
|
||||
self._websocket_onopen = null;
|
||||
self._websocket_onclose = null;
|
||||
self._websocket = null;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype._send = function (msg) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (!self._websocket_connected) {
|
||||
throw "Autobahn not connected";
|
||||
}
|
||||
|
||||
var rmsg = JSON.stringify(msg);
|
||||
self._websocket.send(rmsg);
|
||||
self._txcnt += 1;
|
||||
|
||||
if (ab._debugws) {
|
||||
console.group("WS Send");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(self._txcnt);
|
||||
console.log(rmsg);
|
||||
console.groupEnd();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.close = function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (!self._websocket_connected) {
|
||||
throw "Autobahn not connected";
|
||||
}
|
||||
|
||||
self._websocket.close();
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.sessionid = function () {
|
||||
|
||||
var self = this;
|
||||
return self._session_id;
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.shrink = function (uri, pass) {
|
||||
|
||||
var self = this;
|
||||
return self._prefixes.shrink(uri, pass);
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.resolve = function (curie, pass) {
|
||||
|
||||
var self = this;
|
||||
return self._prefixes.resolve(curie, pass);
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.prefix = function (prefix, uri) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (self._prefixes.get(prefix) !== undefined) {
|
||||
throw "prefix '" + prefix + "' already defined";
|
||||
}
|
||||
|
||||
self._prefixes.set(prefix, uri);
|
||||
|
||||
if (ab._debugrpc || ab._debugpubsub) {
|
||||
console.group("WAMP Prefix");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(prefix);
|
||||
console.log(uri);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
var msg = [ab._MESSAGE_TYPEID_PREFIX, prefix, uri];
|
||||
self._send(msg);
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.call = function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
var d = new when.defer();
|
||||
var callid;
|
||||
while (true) {
|
||||
callid = ab._newid();
|
||||
if (!(callid in self._calls)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self._calls[callid] = d;
|
||||
|
||||
var procuri = self._prefixes.shrink(arguments[0], true);
|
||||
var obj = [ab._MESSAGE_TYPEID_CALL, callid, procuri];
|
||||
for (var i = 1; i < arguments.length; i += 1) {
|
||||
obj.push(arguments[i]);
|
||||
}
|
||||
|
||||
self._send(obj);
|
||||
|
||||
if (ab._debugrpc) {
|
||||
d._ab_callobj = obj;
|
||||
d._ab_tid = self._wsuri + " [" + self._session_id + "][" + callid + "]";
|
||||
console.time(d._ab_tid);
|
||||
console.info();
|
||||
}
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.subscribe = function (topicuri, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
// subscribe by sending WAMP message when topic not already subscribed
|
||||
//
|
||||
var rtopicuri = self._prefixes.resolve(topicuri, true);
|
||||
if (!(rtopicuri in self._subscriptions)) {
|
||||
|
||||
if (ab._debugpubsub) {
|
||||
console.group("WAMP Subscribe");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(topicuri);
|
||||
console.log(callback);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
var msg = [ab._MESSAGE_TYPEID_SUBSCRIBE, topicuri];
|
||||
self._send(msg);
|
||||
|
||||
self._subscriptions[rtopicuri] = [];
|
||||
}
|
||||
|
||||
// add callback to event listeners list if not already in list
|
||||
//
|
||||
var i = self._subscriptions[rtopicuri].indexOf(callback);
|
||||
if (i === -1) {
|
||||
self._subscriptions[rtopicuri].push(callback);
|
||||
}
|
||||
else {
|
||||
throw "callback " + callback + " already subscribed for topic " + rtopicuri;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.unsubscribe = function (topicuri, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var rtopicuri = self._prefixes.resolve(topicuri, true);
|
||||
if (!(rtopicuri in self._subscriptions)) {
|
||||
throw "not subscribed to topic " + rtopicuri;
|
||||
}
|
||||
else {
|
||||
var removed;
|
||||
if (callback !== undefined) {
|
||||
var idx = self._subscriptions[rtopicuri].indexOf(callback);
|
||||
if (idx !== -1) {
|
||||
removed = callback;
|
||||
self._subscriptions[rtopicuri].splice(idx, 1);
|
||||
}
|
||||
else {
|
||||
throw "no callback " + callback + " subscribed on topic " + rtopicuri;
|
||||
}
|
||||
}
|
||||
else {
|
||||
removed = self._subscriptions[rtopicuri].slice();
|
||||
self._subscriptions[rtopicuri] = [];
|
||||
}
|
||||
|
||||
if (self._subscriptions[rtopicuri].length === 0) {
|
||||
|
||||
delete self._subscriptions[rtopicuri];
|
||||
|
||||
if (ab._debugpubsub) {
|
||||
console.group("WAMP Unsubscribe");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(topicuri);
|
||||
console.log(removed);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
var msg = [ab._MESSAGE_TYPEID_UNSUBSCRIBE, topicuri];
|
||||
self._send(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ab.Session.prototype.publish = function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
var topicuri = arguments[0];
|
||||
var event = arguments[1];
|
||||
|
||||
var excludeMe = null;
|
||||
var exclude = null;
|
||||
var eligible = null;
|
||||
|
||||
var msg = null;
|
||||
|
||||
if (arguments.length > 3) {
|
||||
|
||||
if (!(arguments[2] instanceof Array)) {
|
||||
throw "invalid argument type(s)";
|
||||
}
|
||||
if (!(arguments[3] instanceof Array)) {
|
||||
throw "invalid argument type(s)";
|
||||
}
|
||||
|
||||
exclude = arguments[2];
|
||||
eligible = arguments[3];
|
||||
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude, eligible];
|
||||
|
||||
} else if (arguments.length > 2) {
|
||||
|
||||
if (typeof(arguments[2]) === 'boolean') {
|
||||
|
||||
excludeMe = arguments[2];
|
||||
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, excludeMe];
|
||||
|
||||
} else if (arguments[2] instanceof Array) {
|
||||
|
||||
exclude = arguments[2];
|
||||
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude];
|
||||
|
||||
} else {
|
||||
throw "invalid argument type(s)";
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event];
|
||||
}
|
||||
|
||||
if (ab._debugpubsub) {
|
||||
console.group("WAMP Publish");
|
||||
console.info(self._wsuri + " [" + self._session_id + "]");
|
||||
console.log(topicuri);
|
||||
console.log(event);
|
||||
|
||||
if (excludeMe !== null) {
|
||||
console.log(excludeMe);
|
||||
} else {
|
||||
if (exclude !== null) {
|
||||
console.log(exclude);
|
||||
if (eligible !== null) {
|
||||
console.log(eligible);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
self._send(msg);
|
||||
};
|
||||
@@ -87,4 +87,3 @@ Espo.define('crm:views/meeting/popup-notification', 'views/popup-notification',
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+17
-2
@@ -50,7 +50,8 @@ Espo.define(
|
||||
'layout-manager',
|
||||
'theme-manager',
|
||||
'session-storage',
|
||||
'view-helper'
|
||||
'view-helper',
|
||||
'web-socket-manager'
|
||||
],
|
||||
function (
|
||||
Ui,
|
||||
@@ -73,7 +74,8 @@ Espo.define(
|
||||
LayoutManager,
|
||||
ThemeManager,
|
||||
SessionStorage,
|
||||
ViewHelper
|
||||
ViewHelper,
|
||||
WebSocketManager
|
||||
) {
|
||||
|
||||
var App = function (options, callback) {
|
||||
@@ -141,6 +143,10 @@ Espo.define(
|
||||
this.modelFactory = new ModelFactory(this.loader, this.metadata, this.user);
|
||||
this.collectionFactory = new CollectionFactory(this.loader, this.modelFactory);
|
||||
|
||||
if (this.settings.get('useWebSocket')) {
|
||||
this.webSocketManager = new WebSocketManager(this.settings);
|
||||
}
|
||||
|
||||
this.initDateTime();
|
||||
this.initView();
|
||||
this.initBaseController();
|
||||
@@ -223,6 +229,10 @@ Espo.define(
|
||||
this.loadStylesheet();
|
||||
}
|
||||
|
||||
if (this.webSocketManager) {
|
||||
this.webSocketManager.connect(this.auth, this.user.id);
|
||||
}
|
||||
|
||||
var promiseList = [];
|
||||
var aclImplementationClassMap = {};
|
||||
|
||||
@@ -396,6 +406,7 @@ Espo.define(
|
||||
helper.sessionStorage = this.sessionStorage;
|
||||
helper.basePath = this.basePath;
|
||||
helper.appParams = this.appParams;
|
||||
helper.webSocketManager = this.webSocketManager;
|
||||
|
||||
this.viewLoader = function (viewName, callback) {
|
||||
Espo.require(Espo.Utils.composeViewClassName(viewName), callback);
|
||||
@@ -493,6 +504,10 @@ Espo.define(
|
||||
}
|
||||
}
|
||||
|
||||
if (this.webSocketManager) {
|
||||
this.webSocketManager.close();
|
||||
}
|
||||
|
||||
this.auth = null;
|
||||
this.user.clear();
|
||||
this.preferences.clear();
|
||||
|
||||
@@ -38,6 +38,7 @@ Espo.define('views/admin/settings', 'views/settings/record/edit', function (Dep)
|
||||
if (this.getHelper().getAppParam('isRestrictedMode') && !this.getUser().isSuperAdmin()) {
|
||||
this.hideField('cronDisabled');
|
||||
this.hideField('maintenanceMode');
|
||||
this.hideField('useWebSocket');
|
||||
this.setFieldReadOnly('siteUrl');
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ Espo.define('views/notification/badge', 'view', function (Dep) {
|
||||
|
||||
this.notificationSoundsDisabled = this.getConfig().get('notificationSoundsDisabled');
|
||||
|
||||
this.useWebSocket = this.getConfig().get('useWebSocket');
|
||||
|
||||
this.once('remove', function () {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
@@ -65,7 +67,6 @@ Espo.define('views/notification/badge', 'view', function (Dep) {
|
||||
|
||||
this.notificationsCheckInterval = this.getConfig().get('notificationsCheckInterval') || this.notificationsCheckInterval;
|
||||
|
||||
this.popupCheckIteration = 0;
|
||||
this.lastId = 0;
|
||||
this.shownNotificationIds = [];
|
||||
this.closedNotificationIds = [];
|
||||
@@ -143,9 +144,8 @@ Espo.define('views/notification/badge', 'view', function (Dep) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax('Notification/action/notReadCount').done(function (count) {
|
||||
Espo.Ajax.getRequest('Notification/action/notReadCount').done(function (count) {
|
||||
if (!isFirstCheck && count > this.unreadCount) {
|
||||
|
||||
var blockPlayNotificationSound = localStorage.getItem('blockPlayNotificationSound');
|
||||
if (!blockPlayNotificationSound) {
|
||||
this.playSound();
|
||||
@@ -167,32 +167,50 @@ Espo.define('views/notification/badge', 'view', function (Dep) {
|
||||
runCheckUpdates: function (isFirstCheck) {
|
||||
this.checkUpdates(isFirstCheck);
|
||||
|
||||
if (this.useWebSocket) {
|
||||
this.getHelper().webSocketManager.subscribe('newNotification', function (a, b) {
|
||||
console.log(a, b);
|
||||
this.checkUpdates();
|
||||
}.bind(this))
|
||||
return;
|
||||
}
|
||||
this.timeout = setTimeout(function () {
|
||||
this.runCheckUpdates();
|
||||
}.bind(this), this.notificationsCheckInterval * 1000);
|
||||
},
|
||||
|
||||
checkPopupNotifications: function (name) {
|
||||
checkPopupNotifications: function (name, isNotFirstCheck) {
|
||||
var data = this.popupNotificationsData[name] || {};
|
||||
var url = data.url;
|
||||
var interval = data.interval;
|
||||
var disabled = data.disabled || false;
|
||||
|
||||
if (disabled || !url || !interval) return;
|
||||
if (disabled) return;
|
||||
if (data.portalDisabled && this.getUser().isPortal()) return;
|
||||
|
||||
var isFirstCheck = false;
|
||||
if (this.popupCheckIteration == 0) {
|
||||
isFirstCheck = true;
|
||||
var useWebSocket = this.useWebSocket && data.useWebSocket;
|
||||
if (useWebSocket) {
|
||||
var category = 'popupNotifications.' + (data.webSocketCategory || name);
|
||||
this.getHelper().webSocketManager.subscribe(category, function (c, response) {
|
||||
if (!response.list) return;
|
||||
response.list.forEach(function (item) {
|
||||
console.log(item);
|
||||
this.showPopupNotification(name, item);
|
||||
}, this);
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
if (!url || !interval) return;
|
||||
|
||||
(new Promise(function (resolve) {
|
||||
if (this.checkBypass()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
var jqxhr = $.ajax(url).done(function (list) {
|
||||
list.forEach(function (d) {
|
||||
this.showPopupNotification(name, d, isFirstCheck);
|
||||
var jqxhr = Espo.Ajax.getRequest(url).done(function (list) {
|
||||
list.forEach(function (item) {
|
||||
console.log(item);
|
||||
this.showPopupNotification(name, item, isNotFirstCheck);
|
||||
}, this);
|
||||
}.bind(this));
|
||||
|
||||
@@ -200,14 +218,15 @@ Espo.define('views/notification/badge', 'view', function (Dep) {
|
||||
resolve();
|
||||
});
|
||||
}.bind(this))).then(function () {
|
||||
if (useWebSocket) return;
|
||||
|
||||
this.popoupTimeouts[name] = setTimeout(function () {
|
||||
this.popupCheckIteration++;
|
||||
this.checkPopupNotifications(name);
|
||||
this.checkPopupNotifications(name, isNotFirstCheck);
|
||||
}.bind(this), interval * 1000);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
showPopupNotification: function (name, data, isFirstCheck) {
|
||||
showPopupNotification: function (name, data, isNotFirstCheck) {
|
||||
var view = this.popupNotificationsData[name].view;
|
||||
if (!view) return;
|
||||
|
||||
@@ -233,7 +252,7 @@ Espo.define('views/notification/badge', 'view', function (Dep) {
|
||||
notificationData: data.data || {},
|
||||
notificationId: data.id,
|
||||
id: id,
|
||||
isFirstCheck: isFirstCheck
|
||||
isFirstCheck: !isNotFirstCheck,
|
||||
}, function (view) {
|
||||
view.render();
|
||||
this.$popupContainer.removeClass('hidden');
|
||||
|
||||
@@ -126,7 +126,5 @@ Espo.define('views/popup-notification', 'view', function (Dep) {
|
||||
this.trigger('cancel');
|
||||
this.remove();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('web-socket-manager', ['lib!client/lib/autobahn.js'], function () {
|
||||
|
||||
var WebSocketManager = function (config) {
|
||||
this.config = config;
|
||||
var url = this.config.get('webSocketUrl');
|
||||
this.port = 8080;
|
||||
|
||||
if (url) {
|
||||
if (url.indexOf('wss://') === 0) {
|
||||
this.url = url.substr(6);
|
||||
this.protocolPart = 'wss://';
|
||||
} else {
|
||||
this.url = url.substr(5);
|
||||
this.protocolPart = 'ws://';
|
||||
}
|
||||
if (~this.url.indexOf(':')) {
|
||||
this.port = parseInt(this.url.split(':')[1]);
|
||||
}
|
||||
} else {
|
||||
var siteUrl = this.config.get('siteUrl') || '';
|
||||
if (siteUrl.indexOf('https://') === 0) {
|
||||
this.url = siteUrl.substr(8);
|
||||
this.protocolPart = 'wss://';
|
||||
} else {
|
||||
this.url = siteUrl.substr(7);
|
||||
this.protocolPart = 'ws://';
|
||||
}
|
||||
|
||||
if (~this.url.indexOf('/')) {
|
||||
this.url = this.url.substr(0, this.url.indexOf('/'));
|
||||
}
|
||||
}
|
||||
|
||||
if (~this.url.indexOf(':')) {
|
||||
this.url = this.url.substr(0, this.url.indexOf(':'));
|
||||
}
|
||||
};
|
||||
|
||||
_.extend(WebSocketManager.prototype, {
|
||||
|
||||
connect: function (auth, userId) {
|
||||
try {
|
||||
var authArray = Base64.decode(auth).split(':');
|
||||
var username = authArray[0];
|
||||
var authToken = authArray[1];
|
||||
var url = this.protocolPart + this.url + ':' + this.port;
|
||||
|
||||
url += '?authToken=' + authToken + '&userId=' + userId;
|
||||
|
||||
var connection = this.connection = new ab.Session(url,
|
||||
function () {},
|
||||
function () {},
|
||||
{'skipSubprotocolCheck': true}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
this.connection = null;
|
||||
}
|
||||
},
|
||||
|
||||
subscribe: function (category, callback) {
|
||||
if (!this.connection) return;
|
||||
try {
|
||||
this.connection.subscribe(category, callback);
|
||||
} catch (e) {
|
||||
if (e.message) {
|
||||
console.error(e.message);
|
||||
} else {
|
||||
console.error("Coud not subscribe "+category+" to WebSocket.");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unsubscribe: function (category, callback) {
|
||||
if (!this.connection) return;
|
||||
try {
|
||||
this.connection.unsubscribe(category, callback);
|
||||
} catch (e) {
|
||||
if (e.message) {
|
||||
console.error(e.message);
|
||||
} else {
|
||||
console.error("Coud not unsubscribe "+category+" from WebSocket.");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
close: function () {
|
||||
if (!this.connection) return;
|
||||
try {
|
||||
this.connection.close();
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return WebSocketManager;
|
||||
});
|
||||
+3
-1
@@ -31,7 +31,9 @@
|
||||
"phpoffice/phpexcel": "^1.8",
|
||||
"phpoffice/phpspreadsheet": "^1.1",
|
||||
"spatie/async": "dev-for-espocrm",
|
||||
"symfony/process": "4.1.7"
|
||||
"symfony/process": "4.1.7",
|
||||
"cboden/ratchet": "^0.4.1",
|
||||
"react/zmq": "^0.4.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
|
||||
Generated
+792
-2
@@ -4,9 +4,62 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "e0c7c9338da2f7fd2ba01e84154984e4",
|
||||
"content-hash": "4012989bfe64b06071fdaef6540f99ad",
|
||||
"hash": "4a490462a340775bd04c25c154f661ec",
|
||||
"content-hash": "8fa4566afa49ca25e6609e7789221e60",
|
||||
"packages": [
|
||||
{
|
||||
"name": "cboden/ratchet",
|
||||
"version": "v0.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ratchetphp/Ratchet.git",
|
||||
"reference": "0d31f3a8ad4795fd48397712709e55cd07f51360"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/0d31f3a8ad4795fd48397712709e55cd07f51360",
|
||||
"reference": "0d31f3a8ad4795fd48397712709e55cd07f51360",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/psr7": "^1.0",
|
||||
"php": ">=5.4.2",
|
||||
"ratchet/rfc6455": "^0.2",
|
||||
"react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5",
|
||||
"symfony/http-foundation": "^2.6|^3.0|^4.0",
|
||||
"symfony/routing": "^2.6|^3.0|^4.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"
|
||||
}
|
||||
],
|
||||
"description": "PHP WebSocket library",
|
||||
"homepage": "http://socketo.me",
|
||||
"keywords": [
|
||||
"Ratchet",
|
||||
"WebSockets",
|
||||
"server",
|
||||
"sockets",
|
||||
"websocket"
|
||||
],
|
||||
"time": "2017-12-12 00:49:31"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "1.4.0",
|
||||
@@ -567,6 +620,116 @@
|
||||
],
|
||||
"time": "2013-01-12 18:59:04"
|
||||
},
|
||||
{
|
||||
"name": "evenement/evenement",
|
||||
"version": "v3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/igorw/evenement.git",
|
||||
"reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
|
||||
"reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"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"
|
||||
],
|
||||
"time": "2017-07-23 21:35:13"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "1.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "9f83dded91781a01c63574e387eaa769be769115"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
|
||||
"reference": "9f83dded91781a01c63574e387eaa769be769115",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"psr/http-message": "~1.0",
|
||||
"ralouphie/getallheaders": "^2.0.5"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Psr7\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||
"keywords": [
|
||||
"http",
|
||||
"message",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"stream",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2018-12-04 20:46:45"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "1.20.0",
|
||||
@@ -973,6 +1136,56 @@
|
||||
],
|
||||
"time": "2018-01-28 12:37:15"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"time": "2016-08-06 14:39:51"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.0.0",
|
||||
@@ -1059,6 +1272,454 @@
|
||||
],
|
||||
"time": "2017-01-02 13:31:39"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "2.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
|
||||
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~3.7.0",
|
||||
"satooshi/php-coveralls": ">=1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"time": "2016-02-11 07:05:27"
|
||||
},
|
||||
{
|
||||
"name": "ratchet/rfc6455",
|
||||
"version": "0.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ratchetphp/RFC6455.git",
|
||||
"reference": "1612f528c3496ad06e910d0f8b6f16ab97696706"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/1612f528c3496ad06e910d0f8b6f16ab97696706",
|
||||
"reference": "1612f528c3496ad06e910d0f8b6f16ab97696706",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/psr7": "^1.0",
|
||||
"php": ">=5.4.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.8.*",
|
||||
"react/http": "^0.4.1",
|
||||
"react/socket-client": "^0.4.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"
|
||||
}
|
||||
],
|
||||
"description": "RFC6455 WebSocket protocol handler",
|
||||
"homepage": "http://socketo.me",
|
||||
"keywords": [
|
||||
"WebSockets",
|
||||
"rfc6455",
|
||||
"websocket"
|
||||
],
|
||||
"time": "2018-05-02 14:52:00"
|
||||
},
|
||||
{
|
||||
"name": "react/cache",
|
||||
"version": "v0.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/cache.git",
|
||||
"reference": "7d7da7fb7574d471904ba357b39bbf110ccdbf66"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/cache/zipball/7d7da7fb7574d471904ba357b39bbf110ccdbf66",
|
||||
"reference": "7d7da7fb7574d471904ba357b39bbf110ccdbf66",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/promise": "~2.0|~1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Cache\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Async, Promise-based cache interface for ReactPHP",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"caching",
|
||||
"promise",
|
||||
"reactphp"
|
||||
],
|
||||
"time": "2018-06-25 12:52:40"
|
||||
},
|
||||
{
|
||||
"name": "react/dns",
|
||||
"version": "v0.4.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/dns.git",
|
||||
"reference": "0a0bedfec72b38406413c6ea01e1c015bd0bf72b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/dns/zipball/0a0bedfec72b38406413c6ea01e1c015bd0bf72b",
|
||||
"reference": "0a0bedfec72b38406413c6ea01e1c015bd0bf72b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/cache": "^0.5 || ^0.4 || ^0.3",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.1 || ^1.2.1",
|
||||
"react/promise-timer": "^1.2",
|
||||
"react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.2",
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Dns\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Async DNS resolver for ReactPHP",
|
||||
"keywords": [
|
||||
"async",
|
||||
"dns",
|
||||
"dns-resolver",
|
||||
"reactphp"
|
||||
],
|
||||
"time": "2018-11-11 11:21:13"
|
||||
},
|
||||
{
|
||||
"name": "react/event-loop",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/event-loop.git",
|
||||
"reference": "0266aff7aa7b0613b1f38a723e14a0ebc55cfca3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/0266aff7aa7b0613b1f38a723e14a0ebc55cfca3",
|
||||
"reference": "0266aff7aa7b0613b1f38a723e14a0ebc55cfca3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "~1.0 for ExtEventLoop",
|
||||
"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"
|
||||
],
|
||||
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
|
||||
"keywords": [
|
||||
"asynchronous",
|
||||
"event-loop"
|
||||
],
|
||||
"time": "2018-07-11 14:37:46"
|
||||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v2.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise.git",
|
||||
"reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
|
||||
"reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Promise\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||
"keywords": [
|
||||
"promise",
|
||||
"promises"
|
||||
],
|
||||
"time": "2019-01-07 21:25:54"
|
||||
},
|
||||
{
|
||||
"name": "react/promise-timer",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise-timer.git",
|
||||
"reference": "a11206938ca2394dc7bb368f5da25cd4533fa603"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise-timer/zipball/a11206938ca2394dc7bb368f5da25cd4533fa603",
|
||||
"reference": "a11206938ca2394dc7bb368f5da25cd4533fa603",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.7.0 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Promise\\Timer\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
|
||||
"homepage": "https://github.com/reactphp/promise-timer",
|
||||
"keywords": [
|
||||
"async",
|
||||
"event-loop",
|
||||
"promise",
|
||||
"reactphp",
|
||||
"timeout",
|
||||
"timer"
|
||||
],
|
||||
"time": "2018-06-13 16:45:37"
|
||||
},
|
||||
{
|
||||
"name": "react/socket",
|
||||
"version": "v1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/socket.git",
|
||||
"reference": "23b7372bb25cea934f6124f5bdac34e30161959e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/socket/zipball/23b7372bb25cea934f6124f5bdac34e30161959e",
|
||||
"reference": "23b7372bb25cea934f6124f5bdac34e30161959e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"php": ">=5.3.0",
|
||||
"react/dns": "^0.4.13",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.6.0 || ^1.2.1",
|
||||
"react/promise-timer": "^1.4.0",
|
||||
"react/stream": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.2",
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Socket\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
|
||||
"keywords": [
|
||||
"Connection",
|
||||
"Socket",
|
||||
"async",
|
||||
"reactphp",
|
||||
"stream"
|
||||
],
|
||||
"time": "2019-01-07 14:10:13"
|
||||
},
|
||||
{
|
||||
"name": "react/stream",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/stream.git",
|
||||
"reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/stream/zipball/50426855f7a77ddf43b9266c22320df5bf6c6ce6",
|
||||
"reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"php": ">=5.3.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/stream-filter": "~1.2",
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Stream\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"time": "2019-01-01 16:15:09"
|
||||
},
|
||||
{
|
||||
"name": "react/zmq",
|
||||
"version": "v0.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/friends-of-reactphp/zmq.git",
|
||||
"reference": "13dec0bd2397adcc5d6aa54c8d7f0982fba66f39"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/friends-of-reactphp/zmq/zipball/13dec0bd2397adcc5d6aa54c8d7f0982fba66f39",
|
||||
"reference": "13dec0bd2397adcc5d6aa54c8d7f0982fba66f39",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"evenement/evenement": "^3.0 || ^2.0",
|
||||
"ext-zmq": "*",
|
||||
"php": ">=5.4.0",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-pcntl": "*",
|
||||
"phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\ZMQ\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "ZeroMQ bindings for React.",
|
||||
"keywords": [
|
||||
"zeromq",
|
||||
"zmq"
|
||||
],
|
||||
"time": "2018-05-18 15:27:55"
|
||||
},
|
||||
{
|
||||
"name": "slim/slim",
|
||||
"version": "2.6.2",
|
||||
@@ -1159,6 +1820,60 @@
|
||||
],
|
||||
"time": "2018-12-11 08:49:14"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v4.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "a633d422a09242064ba24e44a6e1494c5126de86"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/a633d422a09242064ba24e44a6e1494c5126de86",
|
||||
"reference": "a633d422a09242064ba24e44a6e1494c5126de86",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"symfony/polyfill-mbstring": "~1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"predis/predis": "~1.0",
|
||||
"symfony/expression-language": "~3.4|~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\HttpFoundation\\": ""
|
||||
},
|
||||
"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": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-01-05 16:37:49"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.10.0",
|
||||
@@ -1267,6 +1982,81 @@
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-10-14 20:48:13"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v3.2.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "b382d7c4f443372c118efcd0cd2bf1028434f2f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/b382d7c4f443372c118efcd0cd2bf1028434f2f5",
|
||||
"reference": "b382d7c4f443372c118efcd0cd2bf1028434f2f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5.9"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/config": "<2.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "~1.0",
|
||||
"doctrine/common": "~2.2",
|
||||
"psr/log": "~1.0",
|
||||
"symfony/config": "~2.8|~3.0",
|
||||
"symfony/expression-language": "~2.8|~3.0",
|
||||
"symfony/http-foundation": "~2.8|~3.0",
|
||||
"symfony/yaml": "~2.8|~3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine/annotations": "For using the annotation loader",
|
||||
"symfony/config": "For using the all-in-one router or any loader",
|
||||
"symfony/dependency-injection": "For loading routes from a service",
|
||||
"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",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.2-dev"
|
||||
}
|
||||
},
|
||||
"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": "Symfony Routing Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"router",
|
||||
"routing",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2017-06-23 06:35:45"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v2.7.0",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
if (substr(php_sapi_name(), 0, 3) != 'cli') die('Cron can be run only via CLI');
|
||||
|
||||
include "bootstrap.php";
|
||||
|
||||
$app = new \Espo\Core\Application();
|
||||
$categoryList = array_keys($app->getContainer()->get('metadata')->get(['app', 'webSocket', 'categories'], []));
|
||||
|
||||
$loop = \React\EventLoop\Factory::create();
|
||||
$pusher = new \Espo\Core\WebSocket\Pusher($categoryList);
|
||||
|
||||
$context = new \React\ZMQ\Context($loop);
|
||||
$pull = $context->getSocket(\ZMQ::SOCKET_PULL);
|
||||
$pull->bind('tcp://127.0.0.1:5555');
|
||||
$pull->on('message', [$pusher, 'onMessageReceive']);
|
||||
|
||||
$webSocket = new \React\Socket\Server('0.0.0.0:8080', $loop);
|
||||
$webServer = new \Ratchet\Server\IoServer(
|
||||
new \Ratchet\Http\HttpServer(
|
||||
new \Ratchet\WebSocket\WsServer(
|
||||
new \Ratchet\Wamp\WampServer($pusher)
|
||||
)
|
||||
),
|
||||
$webSocket
|
||||
);
|
||||
|
||||
$loop->run();
|
||||
Reference in New Issue
Block a user