UNPKG

@evpower/ocpp-ts

Version:

OCPP 1.6: Open Charge Point Protocol

150 lines (128 loc) 4.76 kB
import EventEmitter from 'events'; import WebSocket, { WebSocketServer, CLOSING } from 'ws'; import { SecureContextOptions } from 'tls'; import { createServer as createHttpsServer } from 'https'; import { createServer as createHttpServer, IncomingMessage, Server as httpServer, STATUS_CODES } from 'http'; import stream from 'node:stream'; import { OCPP_PROTOCOL_1_6 } from './schemas'; import { Client } from './Client'; import { OcppClientConnection } from '../OcppClientConnection'; import { Protocol } from './Protocol'; import StatusCode from "status-code-enum"; const DEFAULT_PING_INTERVAL = 30; // seconds export class Server extends EventEmitter { private server: httpServer | undefined; private clients: Array<Client> = []; private pingInterval: number = DEFAULT_PING_INTERVAL; // seconds private protocolTimeout: number = 30000; // milliseconds public setPingInterval(pingInterval: number) { this.pingInterval = pingInterval; } public setProtocolTimeout(timeout: number) { this.protocolTimeout = timeout; } protected listen(port = 9220, options?: SecureContextOptions) { if (options) { this.server = createHttpsServer(options || {}); } else { this.server = createHttpServer(); } const wss = new WebSocketServer({ noServer: true, handleProtocols: (protocols: Set<string>) => { if (protocols.has(OCPP_PROTOCOL_1_6)) { return OCPP_PROTOCOL_1_6; } return false; }, }); wss.on('connection', (ws, req) => this.onNewConnection(ws, req)); this.server.on('upgrade', (req: IncomingMessage, socket: stream.Duplex, head: Buffer) => { const cpId = Server.getCpIdFromUrl(req.url); if (!cpId) { socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); socket.destroy(); } else if (this.listenerCount('authorization')) { this.emit('authorization', cpId, req, (status?: StatusCode) => { if (status && status !== StatusCode.SuccessOK) { socket.write(`HTTP/1.1 ${status} ${STATUS_CODES[status]}\r\n\r\n`); socket.destroy(); } else { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req); }); } }); } else { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req); }); } }); this.server.listen(port); } private onNewConnection(socket: WebSocket, req: IncomingMessage) { const cpId = Server.getCpIdFromUrl(req.url); if (!socket.protocol || !cpId) { // From Spec: If the Central System does not agree to using one of the subprotocols offered // by the client, it MUST complete the WebSocket handshake with a response without a // Sec-WebSocket-Protocol header and then immediately close the WebSocket connection. // console.info('Closed connection due to unsupported protocol'); socket.close(); return; } const client = new OcppClientConnection(cpId); client.setHeaders(req.headers); client.setConnection(new Protocol(client, socket, this.protocolTimeout)); let isAlive = true; socket.on('pong', () => { // console.error('received pong from client', cpId); isAlive = true; }); // console.error(`ping interval set to ${this.pingInterval} seconds for ${cpId}`); const pingTimerInterval = setInterval(() => { if (isAlive === false) { // console.error(`did not get pong, terminating connection in under ${this.pingInterval} seconds`, cpId); socket.terminate(); } else if (socket.readyState < CLOSING) { isAlive = false; socket.ping(cpId, false, (err) => { if (err) { // console.info('error on ping', err.message); socket.terminate(); } }); } }, this.pingInterval * 1000); socket.on('error', (err) => { client.emit('error', err); }); socket.on('close', (code: number, reason: Buffer) => { clearInterval(pingTimerInterval); const index = this.clients.indexOf(client); this.clients.splice(index, 1); client.emit('close', code, reason); }); this.clients.push(client); this.emit('connection', client); } protected close() { this.server?.close(); this.clients.forEach((client) => client.close()); } static getCpIdFromUrl(url: string | undefined): string | undefined { try { if (url) { const encodedCpId = url.split('/') .pop(); if (encodedCpId) { return decodeURI(encodedCpId.split('?')[0]); } } } catch (e) { console.error(e); } return undefined; } }