forked from foxssake/natty
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
161 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Noray } from '../noray.mjs' | ||
import logger from '../logger.mjs' | ||
|
||
const log = logger.child({ name: 'Echo' }) | ||
|
||
Noray.hook(noray => { | ||
log.info('Adding echo command') | ||
|
||
noray.protocolServer.on('echo', (data, socket) => { | ||
socket.write(`echo ${data}\n`) | ||
log.info('Echoing: %s', data) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* eslint-disable */ | ||
import * as net from 'node:net' | ||
/* eslint-enable */ | ||
import * as readline from 'node:readline' | ||
import * as events from 'node:events' | ||
|
||
/** | ||
* Protocol implementation. | ||
* | ||
* The "protocol" itself is as follows: | ||
* | ||
* ``` | ||
* <command> <data>\n | ||
* <command>\n | ||
* ``` | ||
* | ||
* If the incoming data fits either of the above formats, an event with the | ||
* command's name is emitted. The data can be an arbitrary string. The same | ||
* applies to the command, with the exception that it can't contain spaces. | ||
*/ | ||
export class ProtocolServer extends events.EventEmitter { | ||
#readers = new Map() | ||
|
||
/** | ||
* Attach socket to server. | ||
* @param {net.Socket} socket | ||
*/ | ||
attach (socket) { | ||
const rl = readline.createInterface({ | ||
input: socket | ||
}) | ||
|
||
rl.on('line', line => this.#handleLine(socket, line)) | ||
this.#readers.set(socket, rl) | ||
} | ||
|
||
/** | ||
* Detach socket from server. | ||
* @param {net.Socket} socket | ||
*/ | ||
detach (socket) { | ||
this.#readers.get(socket)?.close() | ||
this.#readers.delete(socket) | ||
} | ||
|
||
/** | ||
* @param {net.Socket} socket | ||
* @param {string} line | ||
*/ | ||
#handleLine (socket, line) { | ||
const idx = line.indexOf(' ') | ||
|
||
const [command, data] = idx >= 0 | ||
? [line.slice(0, idx), line.slice(idx + 1)] | ||
: [line, ''] | ||
|
||
this.emit(command, data, socket) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { describe, it, beforeEach, afterEach } from 'node:test' | ||
import assert from 'node:assert' | ||
import sinon from 'sinon' | ||
import * as net from 'node:net' | ||
import { ProtocolServer } from '../../../src/protocol/protocol.server.mjs' | ||
import { promiseEvent, sleep } from '../../../src/utils.mjs' | ||
|
||
describe('ProtocolServer', () => { | ||
/** @type {net.Socket} */ | ||
let socket | ||
|
||
/** @type {ProtocolServer} */ | ||
let server | ||
|
||
/** @type {net.Server} */ | ||
let host | ||
|
||
beforeEach(async () => { | ||
server = new ProtocolServer() | ||
|
||
host = net.createServer(conn => server.attach(conn)) | ||
host.listen() | ||
await promiseEvent(host, 'listening') | ||
|
||
socket = net.createConnection(host.address().port) | ||
await promiseEvent(socket, 'connect') | ||
}) | ||
|
||
it('should emit event with data', async () => { | ||
// Given | ||
const handler = sinon.mock() | ||
server.on('command', handler) | ||
|
||
// When | ||
socket.write('command data\n') | ||
await sleep(0.05) | ||
|
||
// Then | ||
assert.equal(handler.args[0][0], 'data') | ||
}) | ||
|
||
it('should emit event without data', async () => { | ||
// Given | ||
const handler = sinon.mock() | ||
server.on('command', handler) | ||
|
||
// When | ||
socket.write('command\n') | ||
await sleep(0.05) | ||
|
||
// Then | ||
assert.equal(handler.args[0][0], '') | ||
}) | ||
|
||
it('should not emit without nl', async () => { | ||
// Given | ||
const handler = sinon.mock() | ||
server.on('command', handler) | ||
|
||
// When | ||
socket.write('command') | ||
await sleep(0.05) | ||
|
||
// Then | ||
assert(handler.notCalled) | ||
}) | ||
|
||
afterEach(() => { | ||
socket.destroy() | ||
host.close() | ||
}) | ||
}) |