В этом разделе мы настроим связь между интерфейсом и сервером с помощью веб-сокетов.

Настройка серверной части

Впервые за долгое время мы вносим изменения в server/index.js. Существуют различные пакеты веб-сокетов для Node. Для нашего относительно простого варианта использования мы просто используем ws. Импортируйте это в верхней части файла:

const {WebSocketServer} = require('ws');

Внизу файла давайте добавим код для проверки концепции. Это запустит сервер веб-сокетов и покажет, когда соединение будет выполнено с внешнего интерфейса:

const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', ws => {
    console.log('A connection!');
    ws.on('message', data => {
        console.log('Data from frontend:', data.toString());
        wss.clients.forEach(client => client.send(data));
    });
});

Настройка внешнего интерфейса

Мы обернем объект WebSocket по умолчанию в класс, чтобы наш код был более организованным. Добавьте это в script.js:

class Socket {
    constructor() {
        this.socket = new WebSocket('ws://localhost:8080');
        this.socket.onopen = e => this.sendDraw();
        this.onmessage = msg => this.handleMessage(msg);
    }
    sendDraw() {
        this.socket.send('this works');
    }
 
    async handleMessage(msg) {
        console.log(await msg.data.text());
    }
}

Затем в index.html добавьте следующее:

const socket = new Socket();

Этот код запускает сокетное соединение и отправляет тестовое сообщение на сервер. Важно, чтобы сервер работал с новым кодом, прежде чем обновлять страницу в браузере, чтобы это работало. После обновления страницы внешний интерфейс отправляет сообщение сокета, которое должно выглядеть примерно так в консоли, где у вас запущен сервер Node:

Затем серверная часть отправляет тестовое сообщение обратно каждому подключенному в данный момент клиенту. Поэтому, если у нас открыто несколько копий внешнего интерфейса на разных вкладках, каждая из них получит сообщение «это работает» при открытии новой вкладки. Если все работает правильно, вы должны увидеть «это работает» в консоли разработчика для используемого вами браузера.

Идентификация себя

Теперь, когда мы можем отправлять сообщения сокетов на сервер и с сервера, мы можем начать совместную часть этого проекта. Для совместной работы требуется пара настроек:

  • Концепция «комнат», которая представляет холст, над которым работают вместе несколько человек.
  • Идентификаторы для каждого человека в комнате

Для этого мы добавляем класс Room на фронтэнд:

class Room {
    constructor() {
        this.getRoom();
        this.id = Math.round(Math.random() * 10000).toString();
    }
    getRoom() {
        const {pathname} = location;
        if (pathname !== '/') {
            this.room = pathname.split('/')[1];
        } else {
            this.room = Math.round(Math.random() * 10000).toString();
            history.pushState({}, '', '/' + this.room);
        }
    }
}

Метод getName выполняет здесь тяжелую работу. Он проверяет URL на наличие имени комнаты. Например, в http://localhost:3000/foo имя комнаты — foo. Вы можете сделать имя комнаты любым, если оно соответствует правилам структуры URL. Если он не находит название комнаты в URL-адресе, он генерирует его случайным образом. В данном случае это число от 0 до 10 000.

Конструктор также генерирует случайный идентификатор для текущей вкладки. Это также случайное число от 0 до 10 000.

Создайте экземпляр объекта Room в файле index.html. Поместите его перед всем остальным Javascript в файле:

const room = new Room();

Теперь, когда мы перезагружаем http://localhost:3000 , мы видим число, прикрепленное к концу URL-адреса. Но есть проблема. Если мы сейчас обновим страницу или введем имя комнаты вручную, мы получим ошибку 404. Это проблема в нашем коде Node. Ни одно из правил маршрутизации не знает, как с этим справиться, поэтому нам нужно его добавить. Добавьте это в функцию обслуживания script.js:

app.get('/:name?', (req, res) => {
    res.sendFile(path.join(__dirname + '/../client/index.html'));
});

Затем перезапустите сервер Node, и теперь он должен работать правильно. Это правило ищет URL-адреса, в которых за http://localhost:3000 следуют буквенно-цифровые символы.

Отправка события розыгрыша

Мы закончим эту статью, просто написав код для отправки события рисования на все открытые вкладки. Что должно произойти, так это следующее: когда пользователь щелкает точку на холсте, объект Canvas должен отправить информацию о щелчке на сервер, используя соответствующие данные из объектов Socket и Room. Чтобы это произошло, сначала нам нужно немного реорганизовать код.

В файле index.html передайте объекты сокета и комнаты в качестве аргументов объекту холста:

const canvas = new Canvas(socket, room);

И сохраните их как свойства в классе Canvas:

class Canvas {
    constructor(socket, room) {
        ...
        this.socket = socket;
        this.room = room;
    }
    ...
}

В Socket мы обновляем метод sendDraw. Мы передаем ему необходимую информацию, чтобы другие клиенты знали, кто отправляет рисунок, и как нарисовать его самостоятельно:

sendDraw(room, mode, color, startPoint, endPoint) {
    this.socket.send(JSON.stringify({
        type: 'draw',
        roomId: room.id,
        roomName: room.room,
        mode,
        color,
        startPoint,
        endPoint
    }));
}

Этот метод вызывается в Canvas.handleDraw . Однако обратите внимание, что его нужно вызывать в операторе else, где this.pointMode означает «конец». Таким образом, сообщение сокета отправляется только при рисовании фигуры. Его также необходимо вызвать до того, как начальная и конечная точки будут установлены обратно в нуль. В противном случае другие клиенты не будут знать, где нарисовать фигуру.

handleDraw(e) {
    ...
    if (...) {
        ...
    } else {
        ...
        this.socket.sendDraw(this.room, this.mode, this.activeColor, this.startPoint, this.endPoint);
        this.startPoint = null;
        this.endPoint = null;
    }
}

Измените строку, говорящую

this.socket.onopen = e => this.sendDraw();

Быть console.log(e); или вообще удалить, если не считаете это полезным.

Собираем вместе

Чтобы протестировать изменения, откройте две вкладки с одинаковым URL-адресом, скажем, http://localhost:3000/1234 . Откройте консоль разработчика для обоих и начните рисовать фигуры на каждой вкладке. То, что вы должны увидеть в консоли на любой из вкладок, — это объекты JSON, описывающие то, что только что было нарисовано.

Вот как выглядит код на данный момент:

В следующем разделе мы посмотрим, как на самом деле рисовать фигуры, полученные с других вкладок, а также очищать холст на каждой вкладке.

Серия приложений для совместного рисования