Когда “еще один пуллинг каждый N секунд” стучится вам в код. Время подумать про вебсокеты a.k.a полнодуплексное соединение.
Речь пойдет про socket.io , не совсем web socket а скорее микс при участии web socket. Но очень удобный в использовании сразу из коробки.
Кейс состоит в следующем:
-
Есть многопользовательское приложение в котором пользователи запускают асинхронные операции на сервере. Другими словами нажимают на кнопку в приложении и ждут когда сервер выполнит все операции а походу еще и расскажет про текущее состояние.
Вроде бы можно обойтись лонг пуллингом, но некоторые действия хочется заблокировать пользователям которые находятся за другими мониторами на той же странице. Да и вообще говоря сама библиотека при отсутствии возможности ws/wss соединения будет использовать пуллинг. Так что вроде бы только плюсы, из минусов только еще один пакет на клиенте и сервере.
Для клиента необходима библиотека socket-io.client, для фронта все написано на React и само соединение можно установить/разорвать через хуки. Для тех кто использует redux-toolkit все можно сделать через RTK.
У вас должен быть базовый объект createApi({ … })
который описывает все эндпоинты и который потом, так же можно расширить.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const baseApi = createApi({ reducerPath: 'baseApi', baseQuery: fetchBaseQuery({ baseUrl: '/' }), endpoints: () => ({}), });
Добавляем в корневое состояния приложения новый редьюсер, если его конечно нет.
const appReducer = combineReducers({ …, [baseApi.reducerPath]: baseApi.reducer, … });
В middleware добавляем => baseApi.middleware
И расширяем свой базовый baseApi функционалом для создания вебсокета.
import { baseApi } from '../api'; import { io } from 'socket.io-client'; export const wsApi = baseApi.injectEndpoints({ endpoints: build => ({ subscribeToEvents: build.query<any, void>({ queryFn: () => ({ data: [] }), async onCacheEntryAdded(_arg, { dispatch, updateCachedData, cacheEntryRemoved }) { // Path is a prefix that will be used right after domain name const socket = io(`${your_url}/events`, { path: '/socket.io', }); socket.on('disconnect', reason => { if (reason === 'io server disconnect') { // the disconnection was initiated by the server, you need to reconnect manually socket.connect(); } // else the socket will automatically try to reconnect }); socket.on(‘EVENT_TYPE’, (event: ServerEvent) => { // Here we should add the logic updateCachedData(draft => { draft.push(event); }); }); await cacheEntryRemoved; socket.close(); }, }), }), overrideExisting: false, }); export const { useSubscribeToEventsQuery } = wsApi;
injectEndpoints
работает как раз для расширения эндпоинтов, только не забудьте добавить overrideExisting: false
чтобы расширить а не переопределить существующий функционал.
useSubscribeToEventsQuery();
можно использовать как обычный хук, в том компоненте в котором желаете подписаться на события, например в App. Еще в api есть свойство keepUnusedDataFor
для того чтобы задать время в секундах существования подключения/данных после последнего unsubscribe, по умолчанию 60 секунд.
На сервере используется Ts.Ed (Node js), но также существуют готовые библиотеки на других языках. Нужно проинсталлировать пакеты связанные с socket.io для сервера. И дела за малым, добавить конфиг:
socketIO: { path: '/socket.io', cors: { origin: '*' // put your servers } }
И добавить сервис который выполняет подключение/отключение клиента а так же отправляет и принимает сообщения.
import { IO, Nsp, Socket, SocketService, SocketSession } from "@tsed/socketio"; import * as SocketIO from "socket.io"; @SocketService("/events") // namespace right after path ‘socket.io’ export class MySocketService { @Nsp nsp: SocketIO.Namespace | undefined; // a map to keep clients by any id you like, a userId or whatever. public clients: Map<string, SocketIO.Socket> = new Map(); constructor(@IO private io: SocketIO.Server) { } /** * Triggered when a new client connects to the Namespace. */ $onConnection(@Socket socket: SocketIO.Socket, @SocketSession session: SocketSession) { this.clients.set(socket.id, socket); } // setup a method to send data to all clients // you can use this from any other service or controller. broadcast(someData: any): void { this.nsp.emit(‘EVENT_TYPE’, { … }: ServerEvent); } // method to send to a targeted client sendToSingleClient(idToSendTo: string, someData: any): void { const socket = this.clients.get(idToSendTo); if (!socket) return; socket.emit(‘EVENT_TYPE’, { … }: ServerEvent); } }
Namespace в socket.io это путь "/event"
после основного пути в настройках socketIO: { path: 'socket.io' }
. Теперь можно в любом необходимом месте заинжектить сервис и отправить сообщение клиентам.
Вообщем то как-то так, после этого клиент может получать сообщения и выполнять необходимые side effects. ??
ссылка на оригинал статьи https://habr.com/ru/articles/594307/
Добавить комментарий