diff --git a/mirzaev/notchat/system/controllers/core.php b/mirzaev/notchat/system/controllers/core.php
index 455c220..fc430f6 100755
--- a/mirzaev/notchat/system/controllers/core.php
+++ b/mirzaev/notchat/system/controllers/core.php
@@ -6,7 +6,10 @@ namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\views\templater,
- mirzaev\notchat\models\core as models;
+ mirzaev\notchat\models\core as models,
+ mirzaev\notchat\models\log,
+ mirzaev\notchat\models\enumerations\log as type,
+ mirzaev\notchat\models\firewall;
// Framework for PHP
use mirzaev\minimal\controller;
@@ -30,7 +33,7 @@ class core extends controller
protected array $errors = [];
/**
- * Constructor of an instance
+ * Constructor
*
* @param bool $initialize Initialize a controller?
*
@@ -44,14 +47,51 @@ class core extends controller
if ($initialize) {
// Initializing is requested
+ // Write to the log of connections
+ log::write(
+ type::CONNECTIONS,
+ trim("[{$_SERVER['REMOTE_ADDR']}] "
+ . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ")
+ . (empty($_SERVER['HTTP_REFERER']) ? '' : "[{$_SERVER['HTTP_REFERER']}] ")
+ . (empty($_SERVER['HTTP_USER_AGENT']) ? '' : "[{$_SERVER['HTTP_USER_AGENT']}]"), ' ')
+ );
+
+ // Initializing of preprocessor of views
+ $this->view = new templater();
+
+ // Checking for ban
+ if (
+ (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && firewall::banned($_SERVER['HTTP_X_FORWARDED_FOR']))
+ || (isset($_SERVER['REMOTE_ADDR']) && firewall::banned($_SERVER['REMOTE_ADDR']))
+ ) {
+ // IP-address is banned
+
+ // Sending a reply
+ echo $this->view->render('pages/ban.html');
+
+ // Exit (success)
+ die;
+ }
+
// Initializing of models core
new models();
-
- // Initializing of preprocessor of views
- $this->view = new templater();
+
+ // Initializing a response headers
+ header('Service-Worker-Allowed: /');
}
}
+ /**
+ * Destructor
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ // Analyze recent requests
+ firewall::analyze();
+ }
+
/**
* Check of initialization
*
@@ -68,5 +108,4 @@ class core extends controller
default => isset($this->{$name})
};
}
-
}
diff --git a/mirzaev/notchat/system/controllers/index.php b/mirzaev/notchat/system/controllers/index.php
index 02f4516..fd645d2 100755
--- a/mirzaev/notchat/system/controllers/index.php
+++ b/mirzaev/notchat/system/controllers/index.php
@@ -6,7 +6,9 @@ namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\controllers\core,
- mirzaev\notchat\models\server;
+ mirzaev\notchat\models\dns,
+ mirzaev\notchat\models\server,
+ mirzaev\notchat\models\text;
/**
* Index controller
@@ -23,6 +25,62 @@ final class index extends core
*/
public function index(array $parameters = []): ?string
{
+ // Initializing a list with languages
+ $this->view->languages = text::list($this->errors);
+
+ // Инициализация бегущей строки
+ $this->view->hotline = [
+ 'id' => 'hotline'
+ ];
+
+ // Инициализация параметров бегущей строки
+ $this->view->hotline = [
+ 'parameters' => [
+ 'step' => '0.3'
+ ]
+ ] + $this->view->hotline;
+
+ // Инициализация аттрибутов бегущей строки
+ $this->view->hotline = [
+ 'attributes' => []
+ ] + $this->view->hotline;
+
+ // Инициализация элементов бегущей строки
+ $this->view->hotline = [
+ 'elements' => [
+ ['html' => ''],
+ [
+ 'tag' => 'article',
+ 'attributes' => [
+ 'class' => 'trash'
+ ],
+ 'html' => $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'trash.html', [
+ 'id' => 'trash_1',
+ 'title' => 'Linoleum',
+ 'main' => '
Do you really like the rotting smell, dull sound and disgusting greasy shine of parquet-like fake pattern on a polymer toxic film? Are you fucking insane?
',
+ 'image' => [
+ 'src' => 'https://virus.mirzaev.sexy/images/trash/linoleum.png',
+ 'alt' => 'Linoleum'
+ ]
+ ])
+ ],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => ''],
+ ['html' => '']
+ ]
+ ] + $this->view->hotline;
+
+
// Exit (success)
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('chats.html');
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $this->view->render('chats.html');
@@ -31,6 +89,106 @@ final class index extends core
return null;
}
+ /**
+ * Render the servers section
+ *
+ * @param array $parameters Parameters of the request (POST + GET)
+ *
+ * @return void Generated JSON to the output buffer
+ */
+ /* public function cache(array $parameters = []): void
+ {
+ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ // GET
+
+ if (file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'manifest')) {
+ // File found
+
+ // Clearing the output buffer
+ if (ob_get_level()) ob_end_clean();
+
+ // Initializing of the output buffer
+ ob_start();
+
+ // Initializing a response headers
+ header('Content-Type: text/cache-manifest');
+
+ // Generating the reponse
+ if ($file = fopen($path, 'r')) {
+ // File open
+
+ // Reading file
+ while (!feof($file)) echo fread($path, 1024);
+
+ // Closing file
+ fclose($file);
+ }
+
+ // Initializing a response headers
+ header('Content-Length: ' . ob_get_length());
+
+ // Sending and deinitializing of the output buffer
+ ob_end_flush();
+ flush();
+ }
+ }
+ } */
+
+ /**
+ *
+ *
+ * @return void Generated JSON to the output buffer
+ */
+ /* public function cache(array $parameters = []): void
+ {
+ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ // GET
+
+ if (file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'cache.js')) {
+ // File found
+
+ // Clearing the output buffer
+ if (ob_get_level()) ob_end_clean();
+
+ // Initializing of the output buffer
+ ob_start();
+
+ // Initializing a response headers
+ header('Content-Type: application/javascript charset=utf-8');
+
+ // Generating the reponse
+ if ($file = fopen($path, 'r')) {
+ // File open
+
+ // Reading file
+ while (!feof($file)) echo fread($file, 1024);
+
+ // Closing file
+ fclose($file);
+ }
+
+ // Initializing a response headers
+ header('Content-Length: ' . ob_get_length());
+
+ // Sending and deinitializing of the output buffer
+ ob_end_flush();
+ flush();
+ }
+ }
+ } */
+
+ /**
+ * Render the offline page
+ */
+ public function offline(): ?string
+ {
+ // Initializing of the title
+ $this->view->title = 'bye';
+
+ // Exit (success)
+ return $this->view->render('pages/offline.html');
+ }
+
/**
* Render the servers section
*
@@ -54,7 +212,17 @@ final class index extends core
// Generating the reponse
echo json_encode(
[
- 'html' => $this->view->render('sections/servers.html', ['current' => isset($parameters['server']) ? server::read($parameters['server'], errors: $this->errors) : null, 'servers' => server::all(100, errors: $this->errors) ?? []]),
+ 'html' => $this->view->render(
+ 'sections/servers.html',
+ [
+ 'current' => isset($parameters['server'])
+ && ($server = server::read(domain: dns::domain($parameters['server'], errors: $this->errors), errors: $this->errors))
+ ? json_decode($server, true, 8)
+ : null,
+ 'servers' => server::all(100, errors: $this->errors) ?? []
+ ]
+ ),
+ 'status' => isset($server) ? 'connected' : 'disconnected',
'errors' => null
]
);
diff --git a/mirzaev/notchat/system/controllers/server.php b/mirzaev/notchat/system/controllers/server.php
index 7f8c086..d219024 100755
--- a/mirzaev/notchat/system/controllers/server.php
+++ b/mirzaev/notchat/system/controllers/server.php
@@ -7,7 +7,13 @@ namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\controllers\core,
mirzaev\notchat\controllers\traits\errors,
- mirzaev\notchat\models\server as model;
+ mirzaev\notchat\models\dns,
+ mirzaev\notchat\models\server as model,
+ mirzaev\notchat\models\log,
+ mirzaev\notchat\models\enumerations\log as type;
+
+// Built-in libraries
+use exception;
/**
* Server controller
@@ -34,7 +40,7 @@ final class server extends core
// POST
// Create a file with server data
- model::write($parameters['domain'], file_get_contents('php://input'), $this->errors);
+ model::write(dns::domain($parameters['server'], errors: $this->errors), file_get_contents('php://input'), $this->errors);
// Initializing a response headers
header('Content-Type: application/json');
@@ -74,11 +80,39 @@ final class server extends core
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST
- // Read a file with server data
- $server = json_decode(model::read(model::domain($parameters['server']), $this->errors), true, 8);
+ // Initializing of buffer of response
+ $return = [];
- // Remove protected parameters
- unset($server['key']);
+ try {
+ // Decode of user input
+ $parameters['server'] = urldecode($parameters['server']);
+
+ // Validation of user input
+ if (mb_strlen($parameters['server']) > 512) throw new exception('Server address longer than 512 characters');
+
+ if ($domain = dns::domain($parameters['server'], errors: $this->errors)) {
+ if ($raw = model::read(domain: $domain)) {
+ // File found and read
+
+ // Decoding server data to remove protected parameters
+ $return['server'] = json_decode($raw, true, 8);
+
+ // Remove protected parameters
+ unset($return['server']['key']);
+ } else throw new exception('Server offline');
+ } else throw new exception('Server not found');
+ } catch (exception $e) {
+ // Write to the buffer of errors
+ $this->errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
// Initializing a response headers
header('Content-Type: application/json');
@@ -90,8 +124,7 @@ final class server extends core
// Generating the reponse
echo json_encode(
- [
- 'server' => $server,
+ $return + [
'errors' => static::text($this->errors)
]
);
diff --git a/mirzaev/notchat/system/models/core.php b/mirzaev/notchat/system/models/core.php
index 4ee9b6e..58af191 100755
--- a/mirzaev/notchat/system/models/core.php
+++ b/mirzaev/notchat/system/models/core.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace mirzaev\notchat\models;
+// Files of the project
+use mirzaev\notchat\models\enumerations\log as type;
+
// Framework for PHP
use mirzaev\minimal\model;
@@ -24,7 +27,12 @@ class core extends model
final public const POSTFIX = '';
/**
- * Path to storage
+ * Path to public directory
+ */
+ final public const PUBLIC = '..' . DIRECTORY_SEPARATOR . 'public';
+
+ /**
+ * Path to storage directory
*/
final public const STORAGE = '..' . DIRECTORY_SEPARATOR . 'storage';
diff --git a/mirzaev/notchat/system/models/dns.php b/mirzaev/notchat/system/models/dns.php
index ba28aae..9b20dac 100755
--- a/mirzaev/notchat/system/models/dns.php
+++ b/mirzaev/notchat/system/models/dns.php
@@ -4,15 +4,14 @@ declare(strict_types=1);
namespace mirzaev\notchat\models;
-// Framework for PHP
-use mirzaev\minimal\model;
+// Files of the project
+use mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
-use exception,
- DirectoryIterator as parser;
+use exception;
/**
- * Core of DNS registry
+ * DNS registry
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich
@@ -41,9 +40,9 @@ class dns extends core
{
try {
// Open file with DNS records
- $dns = fopen(static::DNS, 'r');
+ $dns = fopen(static::DNS, 'c+');
- while (($row = fgets($dns)) !== false) {
+ while (($row = fgets($dns, 512)) !== false) {
// Iterate over rows
// Initializing values of the server data
@@ -52,27 +51,30 @@ class dns extends core
// Incrementing the line read counter
++$line;
- if ($domain === $_domain || $ip === $_ip || $port === $_port) {
- // Server found
+ if ($domain === $_domain || ($port && $ip === $_ip && $port === $_port) || (!$port && $ip === $_ip || $port === $_port)) {
+ // Server found (domain, ip, ip + port)
// Close file with DNS
fclose($dns);
// Exit (success)
- return $record;
+ return array_combine(['domain', 'ip', 'port'], $record);
}
}
// Close file with DNS
fclose($dns);
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
@@ -100,23 +102,23 @@ class dns extends core
// Initializing part the file buffer (rows before target)
$after = [];
+ // Initializing the status that the DNS record has been found
+ $found = false;
+
if (file_exists(static::DNS) && filesize(static::DNS) > 0) {
// File exists and not empty
- // Initializing the status that the DNS record has been found
- $found = false;
-
// Open file with DNS records
- $dns = fopen(static::DNS, 'r');
+ $dns = fopen(static::DNS, 'c+');
- while (($row = fgets($dns)) !== false) {
+ while (($row = fgets($dns, 512)) !== false) {
// Iterate over rows
// Initializing values of the server data
[$_domain] = explode(' ', $row);
// Writing the row to the file buffer (except the target record)
- if ($domain === $_domain) $found = true;
+ if ($domain === $_domain) $found = $row;
else ${$found ? 'after' : 'before'}[] = $row;
}
@@ -131,10 +133,10 @@ class dns extends core
// File locked
// Clear file
- ftruncate($dns, 0);
+ ftruncate($dns, 0);
// Write a new record to the DNS registry
- fwrite($dns, trim(implode("", $before)) . "\n$domain $ip $port\n" . trim(implode("", $after)));
+ fwrite($dns, (count($before) > 0 ? trim(implode("", $before)) . "\n" : '') . "$domain $ip $port" . (count($after) ? "\n" . trim(implode("", $after)) : ''));
// Apply changes
fflush($dns);
@@ -142,14 +144,20 @@ class dns extends core
// Unlock file
flock($dns, LOCK_UN);
}
+
+ // Write to the log
+ log::write(type::DNS, $found ? "[UPDATE] $found -> $domain $ip $port" : "[CREATE] $domain $ip $port");
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
}
@@ -159,32 +167,39 @@ class dns extends core
* Convert domain or IP-address to domain
*
* @param string $server Domain or IP-address of the server
+ * @param bool $strict Check for port compliance?
* @param array &$errors Buffer of errors
*
* @return string|null Domain of the server
*/
- public static function domain(string $server, &$errors = []): ?string
+ public static function domain(string $server, bool $strict = true, &$errors = []): ?string
{
try {
- if (preg_match('/^(https:\/\/)?\d+\..*\d\/?$/', $server) === 1) {
+ if (preg_match('/^(?:https:\/\/)?([\d\.]*)(?:$|:?(\d.*\d)?\/?$)/', $server, $matches) === 1) {
// IP-address
+ // Initializing of parts of address
+ @[, $ip, $port] = $matches;
+
// Exit (success)
- return static::read(ip: $server, errors: $errors)['domain'];
+ return static::read(ip: $ip, port: $strict ? $port : null, errors: $errors)['domain'] ?? null;
} else {
// Domain (implied)
// Exit (success)
- return $server;
+ return $server ?? null;
}
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
@@ -204,25 +219,31 @@ class dns extends core
public static function ip(string $server, &$errors = []): ?string
{
try {
- if (preg_match('/^(https:\/\/)?\d+\..*\d\/?$/', $server) === 1) {
+ if (preg_match('/^(?:https:\/\/)?(\d+\..*):?(\d.*\d)?\/?$/', $server, $matches) === 1) {
// IP-address
+ // Initializing of parts of address
+ [, $ip, $port] = $matches;
+
// Exit (success)
- return $server;
+ return $ip ?? null;
} else {
// Domain (implied)
// Exit (success)
- return static::read(domain: $server, errors: $errors)['ip'];
+ return static::read(domain: $server, errors: $errors)['ip'] ?? null;
}
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
diff --git a/mirzaev/notchat/system/models/enumerations/log.php b/mirzaev/notchat/system/models/enumerations/log.php
new file mode 100755
index 0000000..6dffed1
--- /dev/null
+++ b/mirzaev/notchat/system/models/enumerations/log.php
@@ -0,0 +1,22 @@
+
+ */
+enum log: string
+{
+ case ERRORS = 'errors.log';
+ case CONNECTIONS = 'connections.log';
+ case BANS = 'bans.log';
+ case FIREWALL = 'firewall.log';
+ case SESSIONS = 'sessions.log';
+ case SERVERS = 'servers.log';
+ case DNS = 'dns.log';
+}
diff --git a/mirzaev/notchat/system/models/firewall.php b/mirzaev/notchat/system/models/firewall.php
new file mode 100755
index 0000000..9601e5a
--- /dev/null
+++ b/mirzaev/notchat/system/models/firewall.php
@@ -0,0 +1,211 @@
+
+ */
+class firewall extends core
+{
+ use file, read {
+ file::read as protected file;
+ read::ban as protected _ban;
+ }
+
+ /**
+ * Write
+ *
+ * @param int $range Time range for reading last connections (seconds)
+ * @param int $limit Limit on the number of requests in the allotted time
+ * @param array &$errors Buffer of errors
+ *
+ * @return void
+ *
+ * @todo
+ * 1. Dividing logs based on volume achieved
+ */
+ public static function analyze(int $range = 10, int $limit = 20, &$errors = []): void
+ {
+ try {
+ // Initializing of path to the file with connections
+ $path = log::LOGS . DIRECTORY_SEPARATOR . type::CONNECTIONS->value;
+
+ if (file_exists($path)) {
+ // The file exists
+
+ // Initializing of current time
+ $current = new datetime();
+
+ // Initializing of target past time
+ $past = (clone $current)->modify("-$range seconds");
+
+ // Initializing of the buffer of IP-addresses found in the connections log [ip => amount of connections per $time]
+ $ips = [];
+
+ // Open file with connections
+ $connections = fopen($path, 'r');
+
+ foreach (static::file($connections, 300, 0, -1) as $row) {
+ // Reading a file backwards (rows from end)
+
+ // Skipping of empty rows
+ if (empty($row)) continue;
+
+ try {
+ // Deserializing a row
+ $parameters = static::connection($row, $errors);
+
+ if ($parameters !== null && is_array($parameters)) {
+ // Parameters have been initialized
+
+ // Initializing of parameters of connection
+ [, $date, $ip, $forwarded, $referer, $useragent] = $parameters;
+
+ // Initializing of date of connection
+ $date = DateTime::createFromFormat('Y.m.d H:i:s', $date);
+
+ if (0 <= $elapsed = $date->getTimestamp() - $past->getTimestamp()) {
+ // No more than $range seconds have passed since connection
+
+ // Initializing of counter of connections
+ $ips[$ip] ??= 0;
+
+ // Incrementing of counter of connections
+ ++$ips[$ip];
+ }
+ }
+ } catch (exception $e) {
+ continue;
+ }
+ }
+
+ // Close file with connections
+ fclose($connections);
+ }
+
+ // Ban IP-addresses that do not meet the conditions
+ foreach ($ips ?? [] as $ip => $connections) if ($connections >= $limit) static::ban($ip, new datetime('+2 minutes'));
+ } catch (exception $e) {
+ // Write to buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+ }
+
+ /**
+ * Ban
+ *
+ * @param string $ip IP-address
+ * @param datetime $end Date for unban
+ * @param array &$errors Buffer of errors
+ *
+ * @return void
+ */
+ public static function ban(string $ip, datetime $end, &$errors = []): void
+ {
+ try {
+ // Write to the log of bans
+ log::write(type::BANS, "[{$end->format('Y.m.d H:i:s')}] [$ip]");
+ } catch (exception $e) {
+ // Write to buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+ }
+
+ /**
+ * Check for ban
+ *
+ * Search in the ban log
+ *
+ * @param string $ip IP-address
+ * @param array &$errors Buffer of errors
+ *
+ * @return bool IP-address is banned? (null - has errors)
+ *
+ * @todo Count rows of file for reading instead of constant value (500 rows)
+ */
+ public static function banned(string $ip, &$errors = []): ?bool
+ {
+ try {
+ // Initializing of path to the file with bans
+ $path = log::LOGS . DIRECTORY_SEPARATOR . type::BANS->value;
+
+ if (file_exists($path)) {
+ // The file exists
+
+ // Open file with bans
+ $bans = fopen($path, 'r');
+
+ foreach (static::file($bans, 500, 0, -1) as $row) {
+ // Reading a file backwards (rows from end)
+
+ // Skipping of empty rows
+ if (empty($row)) continue;
+
+ try {
+ // Deserializing a row
+ $parameters = static::_ban($row, $errors);
+
+ if ($parameters !== null && is_array($parameters)) {
+ // Parameters have been initialized
+
+ // Initializing of parameters of connection
+ [, $from, $to, $_ip] = static::_ban($row, $errors);
+
+ // Check for ban and exit (success)
+ if ($ip === $_ip && (new datetime)->getTimestamp() - DateTime::createFromFormat('Y.m.d H:i:s', $to)->getTimestamp() < 0) return true;
+ }
+ } catch (exception $e) {
+ continue;
+ }
+ }
+ }
+
+ // Exit (success)
+ return false;
+ } catch (exception $e) {
+ // Write to buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+
+ // Exit (fail)
+ return null;
+ }
+}
diff --git a/mirzaev/notchat/system/models/log.php b/mirzaev/notchat/system/models/log.php
new file mode 100755
index 0000000..14d1446
--- /dev/null
+++ b/mirzaev/notchat/system/models/log.php
@@ -0,0 +1,72 @@
+
+ */
+class log extends core
+{
+ /**
+ * Path to DNS of storaged servers
+ */
+ final public const LOGS = core::STORAGE . DIRECTORY_SEPARATOR . 'logs';
+
+ /**
+ * Write
+ *
+ * @param type $type Type of log
+ * @param string $value Text to write
+ * @param array &$errors Buffer of errors
+ *
+ * @return void
+ *
+ * @todo
+ * 1. Dividing magazines based on volume achieved
+ */
+ public static function write(type $type, string $value, &$errors = []): void
+ {
+ try {
+ // Initializing of path to the file of the log
+ $path = static::LOGS . DIRECTORY_SEPARATOR . $type->value;
+
+ // Open file of the log
+ $log = fopen($path, 'a');
+
+ if (flock($log, LOCK_EX)) {
+ // File locked
+
+ // Initializing of date
+ $date = date_format(date_create(), 'Y.m.d H:i:s');
+
+ // Write to the log
+ fwrite($log, (filesize($path) === 0 ? '' : PHP_EOL) . "[$date] $value");
+
+ // Apply changes
+ fflush($log);
+
+ // Unlock file
+ flock($log, LOCK_UN);
+ }
+ } catch (exception $e) {
+ // Write to buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+ }
+ }
+}
diff --git a/mirzaev/notchat/system/models/server.php b/mirzaev/notchat/system/models/server.php
index 3f927e3..8984b7e 100755
--- a/mirzaev/notchat/system/models/server.php
+++ b/mirzaev/notchat/system/models/server.php
@@ -4,15 +4,15 @@ declare(strict_types=1);
namespace mirzaev\notchat\models;
-// Framework for PHP
-use mirzaev\minimal\model;
+// Files of the project
+use mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception,
DirectoryIterator as parser;
/**
- * Core of models
+ * Server
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich
@@ -38,6 +38,7 @@ class server extends core
public static function write(string $domain, string $json = '', array &$errors = []): void
{
try {
+ //
if (strlen($domain) > 32) throw new exception('Domain cannot be longer than 32 characters');
// Initializing of path to file
@@ -57,7 +58,7 @@ class server extends core
// File found
// Open file with server data
- $file = fopen($path, "r");
+ $file = fopen($path, "c+");
// Read server data
$old = json_decode(fread($file, filesize($path)), true, 8);
@@ -69,7 +70,7 @@ class server extends core
// The keys match or the file has not been updated for more than 3 days
// Open file with server data
- $file = fopen($path, "w");
+ $file = fopen($path, "c");
// Write server data
fwrite($file, json_encode($new));
@@ -79,12 +80,15 @@ class server extends core
// Write DNS record
dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors);
+
+ // Write to the log of servers
+ log::write(type::SERVERS, "[UPDATE] {$old['domain']} {$old['ip']}:{$old['port']} -> {$new['domain']} {$new['ip']}:{$new['port']}");
} else throw new exception('Public keys do not match');
} else {
// File is not found
// Open file with server data
- $file = fopen($path, "w");
+ $file = fopen($path, "c");
// Write server data
fwrite($file, json_encode($new));
@@ -94,15 +98,21 @@ class server extends core
// Write DNS record
dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors);
+
+ // Write to the log of errors
+ log::write(type::SERVERS, "[CREATE] {$new['domain']} {$new['ip']}:{$new['port']}");
}
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
}
@@ -112,35 +122,47 @@ class server extends core
* Read JSON from file of server
*
* @param string $domain Domain of the server
+ * @param int $time Number of seconds since the file was last edited (86400 seconds is 1 day)
* @param array &$errors Buffer of errors
*
* @return string|null JSON with data of the server
*/
- public static function read(string $domain, &$errors = []): ?string
+ public static function read(string $domain, int $time = 86400, &$errors = []): ?string
{
try {
// Initializing of path to file
$path = static::SERVERS . DIRECTORY_SEPARATOR . "$domain.json";
- // Open file with server data
- $file = fopen($path, "r");
+ if (file_exists($path) && filesize($path) > 0) {
+ // File exists and not empty
- // Read server data
- $server = fread($file, filesize($path));
+ if (time() - filectime($path) < $time && is_readable($path)) {
+ // The file is actual (1 day by default) and writable
- // Close file with server data
- fclose($file);
+ // Open file with server data
+ $file = fopen($path, 'c+');
- // Exit (success)
- return $server;
+ // Read server data
+ $server = fread($file, filesize($path));
+
+ // Close file with server data
+ fclose($file);
+
+ // Exit (success)
+ return $server;
+ }
+ }
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
@@ -175,6 +197,8 @@ class server extends core
$skip = $page * $amount;
foreach (new parser(static::SERVERS) as $file) {
+ // Iterate through all files in a directory
+
// Skipping unnecessary files
if (--$skip > $amount) continue;
@@ -182,31 +206,39 @@ class server extends core
if ($file->isDot()) continue;
if (time() - $file->getCTime() < $time && $file->isReadable()) {
- // The file is actual (3 days by default) and writable
+ // The file is actual (1 day by default) and readable
- // Open file with server data
- $server = $file->openFile('r');
+ if (($size = $file->getSize()) > 0) {
+ // The file is not empty
- // Write server data to the output buffer
- $buffer[] = json_decode($server->fread($file->getSize()));
+ // Open the file with server data
+ $server = $file->openFile('c+');
- // Close file with server data
- unset($file);
+ // Write server data to the output buffer
+ $buffer[] = json_decode($server->fread($size));
+
+ // Close the file with server data
+ unset($file);
+ }
}
+ // Exit (success)
if (--$amount < 1) break;
}
// Exit (success)
return $buffer;
} catch (exception $e) {
- // Write to buffer of errors
+ // Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
diff --git a/mirzaev/notchat/system/models/text.php b/mirzaev/notchat/system/models/text.php
new file mode 100755
index 0000000..4baa5f8
--- /dev/null
+++ b/mirzaev/notchat/system/models/text.php
@@ -0,0 +1,126 @@
+
+ */
+class text extends core
+{
+ /**
+ * Path to the directory with translation files (json)
+ */
+ final public const LANGUAGES = core::PUBLIC . DIRECTORY_SEPARATOR . 'languages';
+
+ /**
+ * Default language
+ */
+ final public const LANGUAGE = 'english';
+
+
+ /**
+ * Read
+ *
+ * @param string $id Identifier
+ * @param string $language Language (name of thw file without ".json")
+ * @param array &$errors Buffer of errors
+ *
+ * @return string|null Text, if found
+ */
+ public static function read(string $id, string $language = 'english', &$errors = []): ?string
+ {
+ try {
+ // Initializing of path to the file of the log
+ $path = static::LANGUAGES . DIRECTORY_SEPARATOR . "$language.json";
+
+ if (file_exists($path)) {
+ // The file exists
+
+ // Open the file of translation
+ $json = file_get_contents($path);
+
+ if (!empty($json)) {
+ // The file is not empty
+
+ // Decoding JSON to Array
+ $text = json_decode($json, true, 8);
+
+ // Exit (success)
+ return $text[$id] ?? throw new exception('Could not find the text in translation file');
+ }
+ }
+ } catch (exception $e) {
+ // Write to buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+
+ // Exit (fail)
+ return null;
+ }
+
+ /**
+ * Generate a list of available languages
+ *
+ * @param array &$errors Buffer of errors
+ *
+ * @return array|null Languages, if they found
+ */
+ public static function list(&$errors = []): ?array
+ {
+ try {
+ // Initializing of the buffer of languages
+ $languages = [];
+
+ foreach (new parser(static::LANGUAGES) as $file) {
+ // Iterate through all files in the languages directory
+
+ // Skipping system shortcuts
+ if ($file->isDot()) continue;
+
+ if ($file->isReadable() && $file->getSize() > 0) {
+ // The file is readable and not empty
+
+ // Write a language to the buffer registry of available languages
+ $languages[] = $file->getBasename('.json');
+ }
+ }
+
+ // Exit (success)
+ return $languages;
+ } catch (exception $e) {
+ // Write to buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+
+ // Exit (fail)
+ return null;
+ }
+}
diff --git a/mirzaev/notchat/system/models/traits/file.php b/mirzaev/notchat/system/models/traits/file.php
new file mode 100755
index 0000000..b8b8505
--- /dev/null
+++ b/mirzaev/notchat/system/models/traits/file.php
@@ -0,0 +1,86 @@
+
+ */
+trait file
+{
+ /**
+ * Read
+ *
+ * @param resource $file File pointer (fopen())
+ * @param int $limit Maximum limit of iterations (rows)
+ * @param int $position Initial cursor position
+ * @param int $step Row reading step
+ * @param array &$errors Buffer of errors
+ *
+ * @return generator|string|null
+ */
+ private static function read($file, int $limit = 500, int $position = 0, int $step = 1, &$errors = []): ?generator
+ {
+ try {
+ while ($limit-- > 0) {
+ // Recursive execution until $limit reaches 0
+
+ // Initializing of the buffer of row
+ $row = '';
+
+ // Initializing the character buffer to generate $row
+ $character = '';
+
+ do {
+ // Iterate over rows
+
+ // End (or beginning) of file reached (success)
+ if (feof($file)) return;
+
+ // Reading a row
+ $row = $character . $row;
+
+ // Move to next position
+ fseek($file, $position += $step, SEEK_END);
+
+ // Read a character
+ $character = fgetc($file);
+
+ // Is the character a carriage return? (end of row)
+ } while ($character != PHP_EOL);
+
+ // Exit (success)
+ yield $row;
+ }
+
+ // Exit (success)
+ return null;
+ } catch (exception $e) {
+ // Write to the buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+
+ // Exit (fail)
+ return null;
+ }
+}
diff --git a/mirzaev/notchat/system/models/traits/log.php b/mirzaev/notchat/system/models/traits/log.php
new file mode 100755
index 0000000..614c298
--- /dev/null
+++ b/mirzaev/notchat/system/models/traits/log.php
@@ -0,0 +1,93 @@
+
+ */
+trait log
+{
+ /**
+ * Deserializing of type::CONNECTIONS
+ *
+ * @param string $row Row from the log type::CONNECTIONS
+ * @param array &$errors Buffer of errors
+ *
+ * @return array [$row, $date, $ip, $forwarded, $referer, $useragent]
+ */
+ private static function connection(string $row, &$errors = []): ?array
+ {
+ try {
+ // Search for parameters of connection
+ preg_match('/(?:^\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]\s?)(?:\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]\s?)?(?:\[([^\]]+)\]\s[^$]?)?(?:\[([^\]]+)\]\s?)?$/', trim($row, PHP_EOL), $matches);
+
+ // Have all 5 parameters been detected?
+ if (count($matches) !== 6) throw new exception('Failed to deserialize row');
+
+ // Exit (success)
+ return $matches;
+ } catch (exception $e) {
+ // Write to the buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ model::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+
+ // Exit (fail)
+ return null;
+ }
+
+ /**
+ * Deserializing of type::BANS
+ *
+ * @param string $row Row from the log type::BANS
+ * @param array &$errors Buffer of errors
+ *
+ * @return array [$row, $from, $to, $ip]
+ */
+ private static function ban(string $row, &$errors = []): ?array
+ {
+ try {
+ // Search for parameters of ban
+ preg_match('/(?:^\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[([^\]]+)\]\s?)$/', trim($row, PHP_EOL), $matches);
+
+ // Have all 3 parameters been detected?
+ if (count($matches) !== 4) throw new exception('Failed to deserialize row');
+
+ // Exit (success)
+ return $matches;
+ } catch (exception $e) {
+ // Write to the buffer of errors
+ $errors[] = [
+ 'text' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'stack' => $e->getTrace()
+ ];
+
+ // Write to the log of errors
+ model::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
+ }
+
+ // Exit (fail)
+ return null;
+ }
+}
diff --git a/mirzaev/notchat/system/public/index.php b/mirzaev/notchat/system/public/index.php
index b7a975c..d4c1116 100755
--- a/mirzaev/notchat/system/public/index.php
+++ b/mirzaev/notchat/system/public/index.php
@@ -21,7 +21,7 @@ define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('INDEX', __DIR__);
// Автозагрузка
-require __DIR__ . DIRECTORY_SEPARATOR
+require INDEX . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
@@ -33,10 +33,14 @@ require __DIR__ . DIRECTORY_SEPARATOR
$router = new router;
// Запись маршрутов
-$router->write('/', 'index', 'index');
+$router->write('/', 'index', 'index', 'GET');
+$router->write('/', 'index', 'index', 'POST');
+$router->write('/manifest', 'index', 'manifest', 'GET');
+$router->write('/offline', 'index', 'offline', 'GET');
+$router->write('/cache.js', 'index', 'cache', 'GET');
$router->write('/server/read/$server', 'server', 'read', 'POST');
+$router->write('/server/write/$server', 'server', 'write', 'POST');
$router->write('/servers', 'index', 'servers', 'POST');
-$router->write('/servers/connect/$domain', 'server', 'write', 'POST');
// Инициализация ядра
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
diff --git a/mirzaev/notchat/system/public/js/cache.js b/mirzaev/notchat/system/public/js/cache.js
new file mode 100644
index 0000000..759fa73
--- /dev/null
+++ b/mirzaev/notchat/system/public/js/cache.js
@@ -0,0 +1,83 @@
+"use strict";
+
+const VERSION = "0.1.0";
+const EXPIRED = 86400000;
+
+self.addEventListener("install", (event) => {
+});
+
+self.addEventListener("fetch", (event) => {
+ event
+ .respondWith(
+ caches
+ .match(event.request)
+ .then((response) => {
+ if (response) {
+ // Found file in cache
+
+ if (
+ Date.now() - new Date(response.headers.get("last-modified")) >
+ EXPIRED
+ ) {
+ // Expired period of storage response
+
+ return fetch(event.request.clone())
+ .then((response) => {
+ if (response.status === 200) {
+ // Downloaded new version
+
+ return caches
+ .open(VERSION)
+ .then((cache) => {
+ // Writing new version to cache
+ cache.put(event.request, response.clone());
+
+ // Exit (success)
+ return response;
+ });
+ } else throw "Failed to download new version";
+ })
+ .catch(() => {
+ // Exit (success) (return old version)
+ return response;
+ });
+ } else return response;
+ } else {
+ // Not found file in cache
+
+ return fetch(event.request.clone())
+ .then((response) => {
+ if (response.status === 200) {
+ // Downloaded
+
+ return caches
+ .open(VERSION)
+ .then((cache) => {
+ // Writing to cache
+ cache.put(event.request, response.clone());
+
+ // Exit (success)
+ return response;
+ });
+ } else throw "Failed to download";
+ });
+ }
+ })
+ .catch(() => {
+ return caches.match("/offline");
+ }),
+ );
+});
+
+/* self.addEventListener("activate", (event) => {
+ event.waitUntil(
+ caches.keys().then((keyList) => {
+ return Promise.all(
+ keyList.map((key) => {
+ // Deleting old versions of cache
+ if (VERSION.indexOf(key) === -1) return caches.delete(key);
+ }),
+ );
+ }),
+ );
+}); */
diff --git a/mirzaev/notchat/system/public/js/chats.js b/mirzaev/notchat/system/public/js/chats.js
index 7a087ee..2b2cff3 100755
--- a/mirzaev/notchat/system/public/js/chats.js
+++ b/mirzaev/notchat/system/public/js/chats.js
@@ -10,27 +10,161 @@ if (typeof window.chats !== "function") {
*/
static server = {
/**
- * Select server
+ * Select a server (dampered)
*
- * @param {string} server Domain or IP-address of the server (from cache by default)
+ * @param {string} server Domain or IP-address:port of the server (from cache by default)
+ * @param {bool} force Force execution
*
* @return {void} Into the document will be generated and injected an HTML-element
*/
- select(server = localStorage.server_ip ?? localStorage.server_domain) {
- if (typeof server === "string" && server.length > 0) {
- if (core.servers instanceof HTMLElement) {
- core.request(`/server/read/${server}`).then((json) => {
- if (
- json.errors !== null && typeof json.errors === "object" &&
- json.errors.length > 0
- ) {} else {
- document.querySelector('figcaption[data-server="ip"]').innerText = `${json.ip}:${json.port}`;
- document.querySelector('figcaption[data-server="description"]').innerText = `${json.description}`;
- }
- });
- }
- }
+ select(
+ server = localStorage.server_ip && localStorage.server_port
+ ? localStorage.server_ip + ":" + localStorage.server_port
+ : localStorage.server_domain,
+ force = false,
+ ) {
+ // Writing status: "connecting"
+ core.menu.setAttribute("data-menu-status", "connecting");
+
+ // Deinitializing animation of opening
+ core.menu.getElementsByTagName("output")[0].classList.remove(
+ "slide-down",
+ );
+
+ // Initializing animation of closing
+ core.menu.getElementsByTagName("output")[0].classList.add(
+ "slide-down-revert",
+ );
+
+ // Disabled for animation
+ /* core.menu.querySelector('figcaption[data-server="domain"]')
+ .innerText =
+ core.menu.querySelector('pre[data-server="description"]')
+ .innerText =
+ ""; */
+
+ this._select(server, force);
},
+
+ /**
+ * Select a server
+ *
+ * @param {string} server Domain or IP-address:port of the server (from cache by default)
+ * @param {bool} force Force execution
+ *
+ * @return {void} Into the document will be generated and injected an HTML-element
+ */
+ _select: damper(
+ (
+ server = localStorage.server_ip && localStorage.server_port
+ ? localStorage.server_ip + ":" + localStorage.server_port
+ : localStorage.server_domain,
+ force = false,
+ ) => {
+ if (server.length > 512) {
+ notifications.write(text.read("CHATS_SERVER_ERROR_LENGTH_MAX"));
+ } else if (typeof server === "string" && server.length > 0) {
+ if (
+ core.menu instanceof HTMLElement &&
+ core.menu.getAttribute("data-menu") === "chats"
+ ) {
+ //
+
+ // Initializing the unlock function
+ function unblock() {
+ // Writing status: "empty"
+ if (
+ core.menu.querySelector('figcaption[data-server="domain"]')
+ .innerText.length === 0 &&
+ core.menu.querySelector('pre[data-server="description"]')
+ .innerText.length === 0
+ ) {
+ core.menu.getElementsByTagName("search")[0]
+ .getElementsByTagName("label")[0].classList.add(
+ "empty",
+ );
+ }
+
+ // Writing status: "disconnected"
+ core.menu.setAttribute("data-menu-status", "disconnected");
+ }
+
+ // Initiating a unlock delay in case the server does not respond
+ const timeout = setTimeout(() => {
+ // this.errors(["Server does not respond"]);
+ unblock();
+ }, 5000);
+
+ core.request(
+ `/server/read/${encodeURIComponent(server)}`,
+ `language=${localStorage.language ?? "english"}`,
+ ).then((json) => {
+ // Deinitializing of unlock delay
+ clearTimeout(timeout);
+
+ if (
+ json.errors !== null && typeof json.errors === "object" &&
+ json.errors.length > 0
+ ) {
+ // Generating notifications with errors
+ // for (const error of json.errors) notifications.write(error);
+
+ // Writing status: "disconnected"
+ core.menu.setAttribute("data-menu-status", "disconnected");
+
+ // Writind domain of the server
+ core.menu.querySelector('figcaption[data-server="domain"]')
+ .innerText = "";
+
+ // Writing description of the server
+ core.menu.querySelector('pre[data-server="description"]')
+ .innerText = "";
+
+ // Writing status: "empty" (for opening the description window)
+ core.menu.getElementsByTagName("search")[0]
+ .getElementsByTagName("label")[0].classList.add(
+ "empty",
+ );
+ } else {
+ // Writing status: "connected"
+ core.menu.setAttribute("data-menu-status", "connected");
+
+ // Writind domain of the server
+ core.menu.querySelector('figcaption[data-server="domain"]')
+ .innerText = `${json.server.domain}`;
+
+ // Writing description of the server
+ core.menu.querySelector('pre[data-server="description"]')
+ .innerText = `${json.server.description}`;
+
+ // Deleting status: "empty" (for opening the description window) (it is implied that the response from the server cannot be empty)
+ core.menu.getElementsByTagName("search")[0]
+ .getElementsByTagName("label")[0].classList.remove(
+ "empty",
+ );
+
+ // Deinitializing animation of closing
+ core.menu.getElementsByTagName("output")[0].classList.remove(
+ "slide-down-revert",
+ );
+
+ // Initializing animation of opening
+ core.menu.getElementsByTagName("output")[0].classList.add(
+ "slide-down",
+ );
+
+ // Writing data of the server to local storage (browser)
+ localStorage.server_domain = json.server.domain;
+ localStorage.server_ip = json.server.ip;
+ localStorage.server_port = json.server.port;
+ }
+ });
+ }
+ }
+ },
+ 800,
+ 1,
+ ),
};
/**
@@ -44,25 +178,40 @@ if (typeof window.chats !== "function") {
*
* @return {void} Into the document will be generated and injected an HTML-element
*/
- servers(server = localStorage.server_ip ?? localStorage.server_domain) {
+ servers(
+ server = `${localStorage.server_ip}:${localStorage.server_port}`,
+ ) {
core.request(
"/servers",
typeof server === "string" && server.length > 0
- ? `server=${server}}`
+ ? `server=${server}`
: "",
).then((json) => {
if (core.servers instanceof HTMLElement) core.servers.remove();
if (
json.errors !== null && typeof json.errors === "object" &&
json.errors.length > 0
- ) {} else {
- const element = document.createElement("div");
- core.header.after(element);
- element.outerHTML = json.html;
+ ) {
+ // Generating notifications with errors
+ for (const error of json.errors) notifications.write(error);
+ } else {
+ if (core.menu instanceof HTMLElement) {
+ // Writing status of connection (hack with replaying animations)
+ core.menu.setAttribute('data-menu-status', 'disconnected');
+ setTimeout(() => core.menu.setAttribute('data-menu-status', json.status ?? 'disconnected'), 100);
- core.servers = document.body.querySelector(
- "section[data-section='servers']",
- );
+ const element = document.createElement("search");
+
+ const search = core.menu.getElementsByTagName("search")[0];
+ if (search instanceof HTMLElement) search.remove();
+
+ core.menu.prepend(element);
+ element.outerHTML = json.html;
+
+ core.menu = document.body.querySelector(
+ "section[data-section='menu']",
+ );
+ }
}
});
},
@@ -77,7 +226,11 @@ if (typeof window.chats !== "function") {
if (
json.errors !== null && typeof json.errors === "object" &&
json.errors.length > 0
- ) {} else {
+ ) {
+ // Generating notifications with errors
+ for (const error of json.errors) notifications.write(error);
+ } else {
+ // сосать бебру
const element = document.createElement("div");
const position = core.main.children.length;
element.style.setProperty("--position", position);
diff --git a/mirzaev/notchat/system/public/js/core.js b/mirzaev/notchat/system/public/js/core.js
index 58a591f..60552e8 100755
--- a/mirzaev/notchat/system/public/js/core.js
+++ b/mirzaev/notchat/system/public/js/core.js
@@ -5,8 +5,11 @@ if (typeof window.core !== "function") {
// Initialize of the class in global namespace
window.core = class core {
- // Label for the element
- static main = document.body.getElementsByTagName('main')[0];
+ // Domain
+ static domain = window.location.hostname;
+
+ // Animations are enabled?
+ static animations = getComputedStyle(document.body).getPropertyValue('--animations') === '1';
// Label for the element
static header = document.body.getElementsByTagName('header')[0];
@@ -14,11 +17,11 @@ if (typeof window.core !== "function") {
// Label for the