Продолжение публикации «Сервер push сообщений»

При разработке клиентской части, не было цели повторить и сделать дизайн как у других.
С другой стороны, поменять и сделать новое расположение областей в десктоп версии сложно. Область контактов слева, сообщения справа, область ввода нового сообщения снизу, сделать лучше и удобней не так просто. Во многих современных решениях дизайн и элементы дизайна едины с мобильными программами. Думаю, именно поэтому, поле ввода сообщения невозможно расширить, сделать две и более строки.



На картинке видна прерывистая линия разделитель, при наведении она становится желто-черно- полосатой. Передвигая разделитель, можно отрегулировать высоту поля для ввода сообщений.

Аналогично можно изменить ширину списка контактов и списка сообщений.

Отправка сообщений анимирована, какой либо анимации при отправке сообщений в других программах не было.

Для фона списка контактов используется градиент серого и розового, похожее сочетание можно встретить на небе в виде зарева.

У пользователей онлайн — оранжевый цвет имени, подключенные комнаты(группы) черного цвета. Пользователи не онлайн и отключенные группы — серого цвета. Отличить комнату от пользователя в списке контактов можно по звездному названию, названия комнат начинаются с ✯



Пользователи могут создавать новые комнаты(группы), добавлять удалять пользователей из групп. Также, есть возможность изменять комнаты, если в списке пользователей комнаты у редактирующего пользователя есть флаг администратор.



Есть возможность пересылки файлов, файлы картинок пересылаются сразу в сообщении и отображаются в сообщении. Видео файлы не пересылаются сразу, отправляется только первый кадр, для скачивания необходимо запустить видео. Остальные файлы аналогично скачиваются только после клика по файлу в сообщении. Максимальный размер файла, а также размер части файла, на которые разбиваются большие файлы, настраивается в push0k admin. Для отправки файла можно воспользоваться кнопкой «отправить вложение», также, можно просто перетащить файл в окно сообщений. Аналогично, присланный файл из сообщения можно перетащить в папку проводника или finder в Mac OS. Иными словами для файлов реализован drag & drop.



В сообщениях удаляются html теги. Но есть похожая на markdown разметка.

* жирный *
~ курсив ~
_ подчеркнутый _

Можно отправлять ссылки, но ссылка должна быть отдельным сообщением.

В каждом сообщении есть кнопки:

“” цитирование сообщения
➦ пересылка сообщения



Для безопасного соединения с сервером реализована кнопка с замком. Логика аналогична браузерам — небезопасный сертификат, просроченный, само подписанный или от другого доменного имени, замок открыт — соединение небезопасно. По кнопке с замком открывается информация сертификата.



Все пользователи сервера видны друг другу, то есть контакты каждого пользователя это остальные пользователи. Пользователи могут переименовывать свои контакты. Есть возможность заблокировать уведомления о новых сообщениях от контакта, также можно полностью заблокировать сообщения от контакта.



В сообщениях не реализована отправка смайлов. Нет видео и аудио связи. Нет возможности демонстрации экрана пользователя. В дальнейшем что-то из перечисленного обязательно реализую.

Скачать push0k client:

windows
Mac OS

Приложение сделано на electron с использованием vue.js. Приложение бесплатно, но в отличии от сервера описанного в первой части статьи, исходный код открывать пока не планирую.

Пример подключения


Подключение состоит из трех частей:

  1. Установка соединения.

    Для защищенного соединения это обмен handshake-сообщениями.
  2. Авторизация.

    При авторизации помимо логина передается хеш от хеша пароля + id соединения.

    В сообщении авторизации передаются параметры компьютера тип ОС, версия ОС, процессор, память, имя компьютера. Эти справочные данные необходимы для статистики и понимания как влияет тип ОС, процессор и память на скорость подключения, обмена данными и.т.д.

    Первые версии программы делались и работали из 1с. Обычно, на одном компьютере может запускаться не одна база данных 1с, а несколько например ЗУП и БП у бухгалтера. Если решение используется для поддержки бухгалтерии, важно понимать, из какой базы отправлено ��ообщение бухгалтера. При авторизации также передаются данные базы данных.
  3. Синхронизация данных.

    После получения сообщения об успешной авторизации, на сервер отправляется сообщение запрос данных с датой. Самая большая дата последнего сообщения или последнего изменения пользователя или комнаты (группы). По переданной дате определяются новые сообщения или измененные пользователи, комнаты(группы). Дата может быть и пустой, тогда получаются все сообщения пользователи и комнаты, такое может быть при первом подключении.

    После получения данных синхронизации, на сервер отправляется сообщения подтверждения получения данных и подсчитанное время «установки соединения», «авторизации», «синхронизации данных».

Подключение socket.io к html странице.


Для node.js

  • открыть консоль
  • перейти в папку проекта команда `cd /yourCatalog`
  • выполнить команду установки `npm install socket.io`

Код примера node.js:

const io = require('socket.io-client');
const crypto = require('crypto');
const os = require('os');
const cpusarray = os.cpus();
let actiontime = 0;
let contime = 0;
let auftime = 0;
let datasintime = 0;
let socket;
let lastdatesync = new Date(0).toISOString();
let usernumber = '+7 (999) 777-77-77';
let pw = 'somePassword';
let baseref = process.cwd();
let basename = 'push0k client';
let baseid = crypto
  .createHash('md5')
  .update(appdirectory)
  .digest('hex');
baseid =
  baseid.substring(0, 8) + '-' + 
  baseid.substring(8, 12) + '-' + 
  baseid.substring(12, 16) + '-' + 
  baseid.substring(16, 20) + '-' + 
  baseid.substring(20, 32);
let basever = '19.02';
let clientid = crypto
  .createHash('md5')
  .update(os.hostname())
  .digest('hex');
clientid =
  clientid.substring(0, 8) + '-' +
  clientid.substring(8, 12) + '-' +
  clientid.substring(12, 16) + '-' +
  clientid.substring(16, 20) + '-' +
  clientid.substring(20, 32);
let syncdata = '';
let syncdatasize = 0;

function sha256(p) {
  const hash = crypto.createHash('sha256');
  hash.update(p);
  return '' + hash.digest('hex');
}

function connect() {
  socket = io('http://yourServer.com:6789', { transports: ['websocket'], timeout: 5000 });
  socket.connect();
  socket.on('connect', onconnect);
  socket.on('message', onmessage);
  actiontime = new Date().getTime();
}

function onconnect() {
  contime = new Date().getTime() - actiontime;
  usernumber = usernumber.replace(/\D/g, '');
  socket
    .binary(false)
    .emit(
      'message',
      '{"event":"auf","user":"' +
        usernumber +
        '","password":"' +
        sha256(pw + socket.id) +
        '","roomsjoin":true,"basename":"' +
        basename +
        '","basever":"' +
        basever +
        '","baseid":"' +
        baseid +
        '","baseref":"' +
        encodeURIComponent(baseref) +
        '","osversion":"' +
        encodeURIComponent(os.release()) +
        '","appversion":"18.08","clientid":"' +
        clientid +
        '","infappview":"","ram":"' +
        os.totalmem() / 1024 / 1024 +
        '","proc":"' +
        encodeURIComponent(cpusarray[0].model) +
        '","ostype":"' +
        encodeURIComponent(os.type() + ' ' + os.arch()) +
        '","compname":"' +
        encodeURIComponent(os.hostname()) +
        '"}'
	);
  // Настройки передачи файлов с сервера
  let filetranfer = msgdata.filetranfer;
  let filemaxsize = msgdata.filemaxsize;
  let filepartsize = msgdata.filepartsize;	
  actiontime = new Date().getTime();
}

function onmessage(msg) {
  let msgdata;
  let mestime = new Date().getTime();
  if (typeof msg === 'string') {
    try {
      msgdata = JSON.parse(msg);
    } catch (err) {
      this.message = err.toString();
      return;
    }
  } else if (typeof msg === 'object') {
    msgdata = msg;
  }
  if (msgdata.event === 'connected') {
    auftime = mestime - actiontime;
    socket
      .binary(false)
      .emit(
        'message',
        '{"event":"getData","userid":"' +
          msgdata.userid +
          '","id":"' +
          msgdata.id +
          '","baseid":"' +
          baseid +
          '","clientid":"' +
          clientid +
          '","lastdatesinc":"' +
          lastdatesync +
          '"}'
      );
    if (msgdata.setpass === 'true') {
      // временный пароль должен быть изменен пользователем
      // в модальном диалоге без возможности отказа
      // openDialogSetNewPassword();
    }
    actiontime = new Date().getTime();
  } else if (msgdata.event === 'datasync') {
    syncdata += msgdata.data;
    syncdatasize += Buffer.byteLength(msg, 'utf8');
    if (msgdata.dataPart < msgdata.partsCount) {
      return;
    }
    datasintime = mestime - actiontime;
    socket
      .binary(false)
      .emit(
        'message',
        '{"event":"dataConfirm","userid":"' +
          msgdata.userid +
          '","dataid":"' +
          msgdata.dataid +
          '","baseid":"' +
          baseid +
          '","contime":' +
          contime +
          ',"auftime":' +
          auftime +
          ',"datasintime":' +
          datasintime +
          ',"datesync":"' +
          msgdata.datesync +
          '","datasize":' +
          syncdatasize +
          '}'
      );
    contime = 0;
    datasintime = 0;
    auftime = 0;
    syncdatasize = 0;
    let datasync = JSON.parse(Buffer.from(syncdata, 'base64').toString('utf8'));
    syncdata = '';
    // обработка полученных данных с сервера
    // datasync.Users массив пользователей
    // datasync.Rooms массив комнат
    // datasync.Cons массив подключений пользователей
    // datasync.joinedRooms массив подключенных комнат
    // datasync.Mess массив сообщений
  }
}