Accept an async listener in the types and normalise any rejection reason

This commit is contained in:
2026-08-27 23:07:48 +02:00
parent 0c55e5ee11
commit 344249b080
7 changed files with 73 additions and 27 deletions
+10
View File
@@ -0,0 +1,10 @@
/** Whatever was thrown or rejected, as an Error. `String()` throws on some values; this cannot. */
export function errorFrom(reason: unknown): Error {
if (reason instanceof Error) return reason;
try {
return new Error(String(reason));
} catch {
return new Error('A thrown value that cannot be converted to a string');
}
}
+16 -4
View File
@@ -9,6 +9,7 @@ import { bindTypeFromCommand, checkSessionOptions, undeclaredInterfaceVersion }
import { createServer as createNetServer } from 'node:net';
import { createServer as createTlsServer } from 'node:tls';
import { defaultInterfaceVersion } from './defs/constants.ts';
import { errorFrom } from './error-from.ts';
import { paramText } from './defs/types.ts';
import { silentLog } from './log.ts';
@@ -50,8 +51,19 @@ const defaults = {
systemId: defaultSystemId,
};
/** A listener may return a promise: an `async` one that rejects is routed like one that throws. */
type ServerListener<K extends keyof ServerEvents> = (...args: ServerEvents[K]) => unknown;
/** A listening SMPP server. Sessions arrive as `session` events; `close()` stops listening. */
export class SmppServer extends EventEmitter<ServerEvents> {
declare addListener: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
declare off: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
declare on: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
declare once: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
declare prependListener: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
declare prependOnceListener: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
declare removeListener: <K extends keyof ServerEvents>(event: K, listener: ServerListener<K>) => this;
readonly sessions = new Set<Session>();
private readonly log: SmppLog;
@@ -78,7 +90,7 @@ export class SmppServer extends EventEmitter<ServerEvents> {
try {
return super.emit(event, ...args);
} catch (thrown: unknown) {
const err = thrown instanceof Error ? thrown : new Error(String(thrown));
const err = errorFrom(thrown);
this.log.error('server - a listener threw', { event, message: err.message });
@@ -95,7 +107,7 @@ export class SmppServer extends EventEmitter<ServerEvents> {
...args: [event: keyof ServerEvents, ...rest: unknown[]]
): void {
const [event] = args;
const error = reason instanceof Error ? reason : new Error(String(reason));
const error = errorFrom(reason);
this.log.error('server - a listener rejected', { event, message: error.message });
@@ -109,7 +121,7 @@ export class SmppServer extends EventEmitter<ServerEvents> {
try {
session.close();
} catch (thrown: unknown) {
this.emit('serverError', thrown instanceof Error ? thrown : new Error(String(thrown)));
this.emit('serverError', errorFrom(thrown));
}
}
@@ -332,7 +344,7 @@ export function server(options: ServerOptions = {}): Promise<Result<{ server: Sm
resolve({ server: smpp });
});
} catch (thrown: unknown) {
onStartupError(thrown instanceof Error ? thrown : new Error(String(thrown)));
onStartupError(errorFrom(thrown));
}
});
}
+15 -3
View File
@@ -15,6 +15,7 @@ import { PduFramer } from './pdu-framer.ts';
import { PendingRequests } from './pending-requests.ts';
import { ReconnectLoop } from './reconnect-loop.ts';
import { SendWindow } from './send-window.ts';
import { errorFrom } from './error-from.ts';
import { optionalParamsMinVersion } from './defs/constants.ts';
import { bindCarries, bindCommands, defaultSystemId, defaults } from './session-options.ts';
import { isResp, objToPdu, pduReturn, pduToObj } from './pdu.ts';
@@ -33,7 +34,18 @@ export type {
export type { BindType };
export { bindCommands, defaultSystemId };
/** A listener may return a promise: an `async` one that rejects is routed like one that throws. */
type SessionListener<K extends keyof SessionEvents> = (...args: SessionEvents[K]) => unknown;
export class Session extends EventEmitter<SessionEvents> {
declare addListener: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
declare off: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
declare on: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
declare once: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
declare prependListener: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
declare prependOnceListener: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
declare removeListener: <K extends keyof SessionEvents>(event: K, listener: SessionListener<K>) => this;
/** Replaced on reconnect, so hold the session rather than this. */
sock: Socket;
readonly log: SmppLog;
@@ -65,7 +77,7 @@ export class Session extends EventEmitter<SessionEvents> {
try {
return super.emit(event, ...args);
} catch (thrown: unknown) {
const err = thrown instanceof Error ? thrown : new Error(String(thrown));
const err = errorFrom(thrown);
this.log.error('session - a listener threw', { event, message: err.message });
@@ -82,7 +94,7 @@ export class Session extends EventEmitter<SessionEvents> {
...args: [event: keyof SessionEvents, ...rest: unknown[]]
): void {
const [event] = args;
const error = reason instanceof Error ? reason : new Error(String(reason));
const error = errorFrom(reason);
this.log.error('session - a listener rejected', { event, message: error.message });
@@ -379,7 +391,7 @@ export class Session extends EventEmitter<SessionEvents> {
this.emit('incomingPduObj', pduObj);
// Every application hook and listener reached from an incoming PDU funnels through here.
void this.incoming.handle(pduObj).catch((thrown: unknown) => {
const err = thrown instanceof Error ? thrown : new Error(String(thrown));
const err = errorFrom(thrown);
this.log.error('session - a handler threw', { message: err.message });
this.emit('sessionError', err);