TypeScript support (definition files) + a few small enhancement proposals (#5)

* add typescript definition files for this JS API

* server.js: replace console.log by debug() call

* server.js: set "iface" and "port" properties on SRTServer listen call
+ and init them with null in constructor

* stream.js: SRTReadStream: init fd property with null in constructor

* stream.js: SRTReadStream: add close method (to free the fd aquired with
connect()), likewise the writable stream methods

* example files: add "use strict"; directive on all

* examples/srt.js: correctly state variable fhandle (this would crash in strict mode
otherwise)

* NAPI: NodeSRT::GetSockOpt: fix error handling to really reach ThrowAsJavaScriptException

* add example: srt.ts (TypeScript usage)

* add .editoconfig to project root (helps keeping files free of whitespaces
and maintain indentation etc)

* package.json: improve metadata quality (via npm init call)

* package.json: add dev-deps for tsc/ts-node and node type-defs

* package.json: add check-tsc script to compile typescript example

* gitignore: add tsc-lib (typescript compiler test output)

* add tsconfig.json (typescript compiler config)

* some end-of-file newline and whitespace fixes

* stream.js: add const decls for all the magic numbers in SRTReadStream

* enable eslint static analysis to find possible issues upfront
+ and maintain codestyle

* update package-lock

* stream.js: fix whitespace and eof

* add lint script for src, examples and types modules

* types: lint error fix
This commit is contained in:
Stephan Hesse 2020-07-20 11:41:15 +02:00 committed by GitHub
parent 25db0b005b
commit 7973c63f2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1320 additions and 28 deletions

20
.editorconfig Normal file
View file

@ -0,0 +1,20 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{cc,mjs,js,jsx,ts,tsx,json,py,yml,md}]
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Tab indentation (no size specified)
[Makefile]
indent_style = tab

23
.eslintrc.js Normal file
View file

@ -0,0 +1,23 @@
module.exports = {
root: true,
"env": {
"commonjs": true,
"es2020": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 11
},
"plugins": ["@typescript-eslint"],
"rules": {
"indent": ["error", 2],
"semi": "error",
"@typescript-eslint/no-var-requires": 0,
"no-constant-condition": 0
}
};

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
node_modules
build
tsc-lib
deps
output
.vscode

View file

@ -1,3 +1,5 @@
"use strict";
const fs = require('fs');
const { SRTWriteStream } = require('../index.js');
@ -8,9 +10,9 @@ srt.connect(writeStream => {
});
srt.on('error', err => {
console.error(err.message);
})
});
process.on('SIGINT', () => {
console.log("Closing connection");
srt.close();
});
});

View file

@ -1,10 +1,12 @@
"use strict";
const { SRT } = require('../index.js');
const srt = new SRT();
const socket = srt.createSocket();
if (socket !== -1) {
console.log("Created socket: " + socket);
}
}
let result;
@ -41,7 +43,7 @@ while (true) {
} else {
while (true) {
const chunk = srt.read(event.socket, 1316);
console.log("Read chunk: " + chunk.length);
console.log("Read chunk: " + chunk.length);
}
}
});

View file

@ -1,3 +1,5 @@
"use strict";
const fs = require('fs');
const dest = fs.createWriteStream('./output');
const { SRTReadStream } = require('../index.js');
@ -17,4 +19,4 @@ if (process.argv[2] === "listener") {
srt.connect(readStream => {
readStream.pipe(dest);
});
}
}

View file

@ -1,3 +1,5 @@
"use strict";
const fs = require('fs');
const { SRTReadStream } = require('../index.js');

View file

@ -1,10 +1,12 @@
"use strict";
const { SRT } = require('../index.js');
const srt = new SRT();
const socket = srt.createSocket();
if (socket !== -1) {
console.log("Created socket: " + socket);
}
}
let result;
result = srt.bind(socket, "0.0.0.0", 1234);
@ -22,10 +24,10 @@ if (!result) {
}
console.log("Waiting for client to connect");
fhandle = srt.accept(socket);
const fhandle = srt.accept(socket);
if (fhandle) {
console.log("Client connected");
const chunk = srt.read(fhandle, 1316);
console.log("Read chunk: " + chunk.length);
}
}

31
examples/srt.ts Normal file
View file

@ -0,0 +1,31 @@
import {SRT} from '../index';
const srt = new SRT();
const socket = srt.createSocket();
if (socket !== -1) {
console.log("Created socket: " + socket);
}
let result;
result = srt.bind(socket, "0.0.0.0", 1234);
if (!result) {
console.log("Bind success");
} else {
console.log(result);
}
result = srt.listen(socket, 2);
if (!result) {
console.log("Listen success");
} else {
console.log(result);
}
console.log("Waiting for client to connect");
const fhandle = srt.accept(socket);
if (fhandle) {
console.log("Client connected");
const chunk = srt.read(fhandle, 1316);
console.log("Read chunk: " + chunk.length);
}

View file

@ -1,3 +1,5 @@
"use strict";
const fs = require('fs');
const source = fs.createReadStream(process.argv[2], { highWaterMark: 1316 });
const { SRTWriteStream } = require('../index.js');
@ -5,4 +7,4 @@ const { SRTWriteStream } = require('../index.js');
const srt = new SRTWriteStream('127.0.0.1', 1234);
srt.connect(writeStream => {
source.pipe(writeStream);
});
});

5
index.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/// <reference path="./types/srt-api.d.ts" />
/// <reference path="./types/srt-stream.d.ts" />
/// <reference path="./types/srt-server.d.ts" />
export * from "srt";

884
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,11 +9,13 @@
"rebuild": "node-gyp rebuild",
"clean": "node-gyp clean",
"test": "$(npm bin)/jasmine",
"lint": "eslint src examples types --ext .js --ext .ts",
"check-tsc": "./node_modules/.bin/tsc examples/srt.ts --outDir ./tsc-lib",
"postversion": "git push && git push --tags"
},
"repository": {
"type": "git",
"url": "https://github.com/Eyevinn/node-srt"
"url": "git+https://github.com/Eyevinn/node-srt.git"
},
"gypfile": true,
"author": "Eyevinn Technology AB <work@eyevinn.se>",
@ -23,12 +25,37 @@
],
"license": "MIT",
"devDependencies": {
"@types/node": "^14.0.23",
"@typescript-eslint/eslint-plugin": "^3.6.1",
"@typescript-eslint/parser": "^3.6.1",
"eslint": "^7.4.0",
"jasmine": "^3.5.0",
"node-gyp": "^7.0.0"
"node-gyp": "^7.0.0",
"ts-node": "^8.10.2",
"typescript": "^3.9.6"
},
"dependencies": {
"debug": "^4.1.1",
"git-clone": "^0.1.0",
"node-addon-api": "^3.0.0"
}
},
"bugs": {
"url": "https://github.com/Eyevinn/node-srt/issues"
},
"homepage": "https://github.com/Eyevinn/node-srt#readme",
"directories": {
"example": "examples"
},
"keywords": [
"SRT",
"Secure",
"Reliable",
"Transport",
"Network",
"Protocol",
"UDP",
"Retransmission",
"Congestion",
"Control"
]
}

View file

@ -261,6 +261,8 @@ Napi::Value NodeSRT::GetSockOpt(const Napi::CallbackInfo& info) {
Napi::Number option = info[1].As<Napi::Number>();
Napi::Value empty;
Napi::Value returnVal;
int32_t optName = option;
int result = SRT_ERROR;
@ -279,7 +281,7 @@ Napi::Value NodeSRT::GetSockOpt(const Napi::CallbackInfo& info) {
case SRTO_KMSTATE:
case SRTO_LATENCY:
case SRTO_LOSSMAXTTL:
case SRTO_MAXBW:
case SRTO_MAXBW:
case SRTO_MINVERSION:
case SRTO_OHEADBW:
case SRTO_PAYLOADSIZE:
@ -306,7 +308,7 @@ Napi::Value NodeSRT::GetSockOpt(const Napi::CallbackInfo& info) {
int optValue;
int optSize = sizeof(optValue);
result = srt_getsockflag(socketValue, (SRT_SOCKOPT)optName, (void *)&optValue, &optSize);
return Napi::Value::From(env, optValue);
returnVal = Napi::Value::From(env, optValue);
}
case SRTO_RCVSYN:
case SRTO_MESSAGEAPI:
@ -318,7 +320,7 @@ Napi::Value NodeSRT::GetSockOpt(const Napi::CallbackInfo& info) {
bool optValue;
int optSize = sizeof(optValue);
result = srt_getsockflag(socketValue, (SRT_SOCKOPT)optName, (void *)&optValue, &optSize);
return Napi::Value::From(env, optValue);
returnVal = Napi::Value::From(env, optValue);
}
case SRTO_PACKETFILTER:
case SRTO_PASSPHRASE:
@ -326,7 +328,7 @@ Napi::Value NodeSRT::GetSockOpt(const Napi::CallbackInfo& info) {
char optValue[512];
int optSize = sizeof(optValue);
result = srt_getsockflag(socketValue, (SRT_SOCKOPT)optName, (void *)&optValue, &optSize);
return Napi::Value::From(env, std::string(optValue));
returnVal = Napi::Value::From(env, std::string(optValue));
}
default:
Napi::Error::New(env, "SOCKOPT not implemented yet").ThrowAsJavaScriptException();
@ -337,7 +339,7 @@ Napi::Value NodeSRT::GetSockOpt(const Napi::CallbackInfo& info) {
Napi::Error::New(env, srt_getlasterror_str()).ThrowAsJavaScriptException();
return empty;
}
return empty;
return returnVal;
}
Napi::Value NodeSRT::GetSockState(const Napi::CallbackInfo& info) {

View file

@ -1,11 +1,14 @@
const { SRT } = require('../build/Release/node_srt.node');
const EventEmitter = require("events");
const debug = require('debug')('srt-server');
const libSRT = new SRT();
class SRTServer extends EventEmitter {
constructor() {
super();
this.iface = null;
this.port = null;
this.socket = libSRT.createSocket();
}
@ -13,12 +16,14 @@ class SRTServer extends EventEmitter {
const iface = address || "0.0.0.0";
libSRT.bind(this.socket, iface, port);
libSRT.listen(this.socket, 2);
this.iface = address;
this.port = port;
this.emit("listening", iface, port);
while (true) {
const fhandle = libSRT.accept(this.socket);
console.log("Client connected");
debug("New client connected");
}
}
}
module.exports = SRTServer;
module.exports = SRTServer;

View file

@ -1,37 +1,44 @@
const { Readable, Writable } = require('stream');
const { Readable, Writable } = require('stream');
const LIB = require('../build/Release/node_srt.node');
const debug = require('debug')('srt-stream');
const EPOLLUWAIT_CALL_PERIOD_MS = 500;
const EPOLLUWAIT_TIMEOUT_MS = 1000;
const SOCKET_LISTEN_BACKLOG = 10;
/**
* Example:
*
*
* const dest = fs.createWritableStream('./output');
*
*
* const srt = new SRTReadStream('0.0.0.0', 1234);
* srt.listen(readStream => {
* readStream.pipe(dest);
* })
*
*
*/
class SRTReadStream extends Readable {
// Q: opts not used ?
// Q: not better if port (mandatory) is before, and address is optional (default to "0.0.0.0")?
constructor(address, port, opts) {
super();
this.srt = new LIB.SRT();
this.socket = this.srt.createSocket();
this.address = address;
this.port = port;
this.fd = null;
this.readTimer = null;
}
listen(cb) {
this.srt.bind(this.socket, this.address, this.port);
this.srt.listen(this.socket, 10);
this.srt.listen(this.socket, SOCKET_LISTEN_BACKLOG);
const epid = this.srt.epollCreate();
this.srt.epollAddUsock(epid, this.socket, LIB.SRT.EPOLL_IN | LIB.SRT.EPOLL_ERR);
let t = setInterval(() => {
const events = this.srt.epollUWait(epid, 1000);
const events = this.srt.epollUWait(epid, EPOLLUWAIT_TIMEOUT_MS);
events.forEach(event => {
const status = this.srt.getSockState(event.socket);
if (status === LIB.SRT.SRTS_BROKEN || status === LIB.SRT.SRTS_NONEXIST || status === LIB.SRT.SRTS_CLOSED) {
@ -50,7 +57,7 @@ class SRTReadStream extends Readable {
cb(this);
}
});
}, 500);
}, EPOLLUWAIT_CALL_PERIOD_MS);
}
connect(cb) {
@ -61,6 +68,11 @@ class SRTReadStream extends Readable {
}
}
close() {
this.srt.close(this.socket);
this.fd = null;
}
_readStart(fd, size) {
this.readTimer = setInterval(() => {
let chunk = this.srt.read(fd, size);
@ -120,4 +132,4 @@ class SRTWriteStream extends Writable {
module.exports = {
SRTReadStream,
SRTWriteStream
}
};

6
tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"allowJs": true
},
"include": ["src", "types", "examples"]
}

189
types/srt-api.d.ts vendored Normal file
View file

@ -0,0 +1,189 @@
declare module "srt" {
class SRT {
/**
*
* @param sender
* @returns SRTSOCKET identifier (integer value)
*/
createSocket(sender?: boolean): number
/**
*
* @param socket
* @param address
* @param port
*/
bind(socket: number, address: string, port: number): SRTResult
/**
*
* @param socket
* @param backlog
*/
listen(socket: number, backlog: number): SRTResult
/**
*
* @param socket
* @param host
* @param port
*/
connect(socket: number, host: string, port: number): SRTResult
/**
*
* @param socket
* @returns File descriptor of incoming connection pipe
*/
accept(socket: number): SRTFileDescriptor
/**
*
* @param socket
*/
close(socket: number): SRTResult
/**
*
* @param socket
* @param chunkSize
*/
read(socket: number, chunkSize: number): Buffer
/**
*
* @param socket
* @param chunk
*/
write(socket: number, chunk: Buffer): SRTResult
/**
*
* @param socket
* @param option
* @param value
*/
setSockOpt(socket: number, option: number, value: SRTSocketOptValue): number
/**
*
* @param socket
* @param option
*/
getSockOpt(socket: number, option: number): SRTSocketOptValue
/**
*
* @param socket
*/
getSockState(socket: number): SRTSockStatus
/**
* @returns epid
*/
epollCreate(): number
/**
*
* @param epid
* @param socket
* @param events
*/
epollAddUsock(epid: number, socket: number, events: number): SRTResult
/**
*
* @param epid
* @param msTimeOut
*/
epollUWait(epid: number, msTimeOut: number): SRTPollingEvent[]
}
interface SRTPollingEvent {
socket: SRTFileDescriptor
events: number
}
type SRTFileDescriptor = number;
type SRTSocketOptValue = boolean | number | string
enum SRTResult {
SRT_ERROR = -1,
SRT_OK = 0
}
enum SRTSockOpt {
SRTO_MSS = 0, // the Maximum Transfer Unit
SRTO_SNDSYN = 1, // if sending is blocking
SRTO_RCVSYN = 2, // if receiving is blocking
SRTO_ISN = 3, // Initial Sequence Number (valid only after srt_connect or srt_accept-ed sockets)
SRTO_FC = 4, // Flight flag size (window size)
SRTO_SNDBUF = 5, // maximum buffer in sending queue
SRTO_RCVBUF = 6, // UDT receiving buffer size
SRTO_LINGER = 7, // waiting for unsent data when closing
SRTO_UDP_SNDBUF = 8, // UDP sending buffer size
SRTO_UDP_RCVBUF = 9, // UDP receiving buffer size
// XXX Free space for 2 options
// after deprecated ones are removed
SRTO_RENDEZVOUS = 12, // rendezvous connection mode
SRTO_SNDTIMEO = 13, // send() timeout
SRTO_RCVTIMEO = 14, // recv() timeout
SRTO_REUSEADDR = 15, // reuse an existing port or create a new one
SRTO_MAXBW = 16, // maximum bandwidth (bytes per second) that the connection can use
SRTO_STATE = 17, // current socket state, see UDTSTATUS, read only
SRTO_EVENT = 18, // current available events associated with the socket
SRTO_SNDDATA = 19, // size of data in the sending buffer
SRTO_RCVDATA = 20, // size of data available for recv
SRTO_SENDER = 21, // Sender mode (independent of conn mode), for encryption, tsbpd handshake.
SRTO_TSBPDMODE = 22, // Enable/Disable TsbPd. Enable -> Tx set origin timestamp, Rx deliver packet at origin time + delay
SRTO_LATENCY = 23, // NOT RECOMMENDED. SET: to both SRTO_RCVLATENCY and SRTO_PEERLATENCY. GET: same as SRTO_RCVLATENCY.
SRTO_TSBPDDELAY = 23, // DEPRECATED. ALIAS: SRTO_LATENCY
SRTO_INPUTBW = 24, // Estimated input stream rate.
SRTO_OHEADBW, // MaxBW ceiling based on % over input stream rate. Applies when UDT_MAXBW=0 (auto).
SRTO_PASSPHRASE = 26, // Crypto PBKDF2 Passphrase size[0,10..64] 0:disable crypto
SRTO_PBKEYLEN, // Crypto key len in bytes {16,24,32} Default: 16 (128-bit)
SRTO_KMSTATE, // Key Material exchange status (UDT_SRTKmState)
SRTO_IPTTL = 29, // IP Time To Live (passthru for system sockopt IPPROTO_IP/IP_TTL)
SRTO_IPTOS, // IP Type of Service (passthru for system sockopt IPPROTO_IP/IP_TOS)
SRTO_TLPKTDROP = 31, // Enable receiver pkt drop
SRTO_SNDDROPDELAY = 32, // Extra delay towards latency for sender TLPKTDROP decision (-1 to off)
SRTO_NAKREPORT = 33, // Enable receiver to send periodic NAK reports
SRTO_VERSION = 34, // Local SRT Version
SRTO_PEERVERSION, // Peer SRT Version (from SRT Handshake)
SRTO_CONNTIMEO = 36, // Connect timeout in msec. Ccaller default: 3000, rendezvous (x 10)
// deprecated: SRTO_TWOWAYDATA, SRTO_SNDPBKEYLEN, SRTO_RCVPBKEYLEN (@c below)
_DEPRECATED_SRTO_SNDPBKEYLEN = 38, // (needed to use inside the code without generating -Wswitch)
//
SRTO_SNDKMSTATE = 40, // (GET) the current state of the encryption at the peer side
SRTO_RCVKMSTATE, // (GET) the current state of the encryption at the agent side
SRTO_LOSSMAXTTL, // Maximum possible packet reorder tolerance (number of packets to receive after loss to send lossreport)
SRTO_RCVLATENCY, // TsbPd receiver delay (mSec) to absorb burst of missed packet retransmission
SRTO_PEERLATENCY, // Minimum value of the TsbPd receiver delay (mSec) for the opposite side (peer)
SRTO_MINVERSION, // Minimum SRT version needed for the peer (peers with less version will get connection reject)
SRTO_STREAMID, // A string set to a socket and passed to the listener's accepted socket
SRTO_CONGESTION, // Congestion controller type selection
SRTO_MESSAGEAPI, // In File mode, use message API (portions of data with boundaries)
SRTO_PAYLOADSIZE, // Maximum payload size sent in one UDP packet (0 if unlimited)
SRTO_TRANSTYPE = 50, // Transmission type (set of options required for given transmission type)
SRTO_KMREFRESHRATE, // After sending how many packets the encryption key should be flipped to the new key
SRTO_KMPREANNOUNCE, // How many packets before key flip the new key is annnounced and after key flip the old one decommissioned
SRTO_ENFORCEDENCRYPTION, // Connection to be rejected or quickly broken when one side encryption set or bad password
SRTO_IPV6ONLY, // IPV6_V6ONLY mode
SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms]
// (some space left)
SRTO_PACKETFILTER = 60 // Add and configure a packet filter
}
enum SRTSockStatus {
SRTS_INIT = 1,
SRTS_OPENED,
SRTS_LISTENING,
SRTS_CONNECTING,
SRTS_CONNECTED,
SRTS_BROKEN,
SRTS_CLOSING,
SRTS_CLOSED,
SRTS_NONEXIST
}
}

22
types/srt-server.d.ts vendored Normal file
View file

@ -0,0 +1,22 @@
/// <reference types="node" />
declare module "srt" {
import {EventEmitter} from 'events';
interface SRTServerBindOpts {
/**
* default: "0.0.0.0"
*/
address?: string
port: number
}
type SRTServerEvent = "listening"; /*| "foobar" */
class SRTServer extends EventEmitter /*<SRTServerEvent>*/ {
listen(opts: SRTServerBindOpts): void
}
}

53
types/srt-stream.d.ts vendored Normal file
View file

@ -0,0 +1,53 @@
import { Writable, Readable } from "stream";
declare module "srt" {
interface SRTConnectionState {
readonly srt: SRT;
readonly socket: number;
readonly address: string;
readonly port: number;
}
interface SRTCallerState extends SRTConnectionState {
readonly fd: SRTFileDescriptor | null;
connect(callback: (state: SRTCallerState) => void);
close();
}
interface SRTListenerState extends SRTConnectionState {
listen(callback: (state: SRTListenerState) => void);
}
class SRTReadStream extends Readable implements SRTCallerState, SRTListenerState {
readonly srt: SRT;
readonly fd: SRTFileDescriptor | null;
readonly socket: number;
readonly address: string;
readonly port: number;
readonly readTimer: number | null;
constructor(address: string, port: number, opts?: unknown);
connect(callback: (state: SRTCallerState) => void);
close();
listen(callback: (state: SRTListenerState) => void);
}
class SRTWriteStream extends Writable implements SRTCallerState {
readonly srt: SRT;
readonly socket: number;
readonly address: string;
readonly port: number;
readonly fd: SRTFileDescriptor | null;
constructor(address: string, port: number, opts?: unknown);
connect(callback: (state: SRTCallerState) => void);
close();
}
}