websocket

This commit is contained in:
yuri
2019-02-01 15:42:18 +02:00
parent a42afcf6e4
commit f1d8af128d
29 changed files with 2479 additions and 37 deletions
+2
View File
@@ -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')
);
}
}
+237
View File
@@ -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)
+59
View File
@@ -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;
+824
View File
@@ -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
View File
@@ -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();
+1
View File
@@ -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');
}
+34 -15
View File
@@ -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');
-2
View File
@@ -126,7 +126,5 @@ Espo.define('views/popup-notification', 'view', function (Dep) {
this.trigger('cancel');
this.remove();
}
});
});
+126
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+55
View File
@@ -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();