@digitaldefiance/node-express-suite 3.12.11 → 3.12.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/src/interfaces/index.d.ts +1 -0
- package/src/interfaces/index.d.ts.map +1 -1
- package/src/interfaces/index.js +1 -0
- package/src/interfaces/index.js.map +1 -1
- package/src/interfaces/network/index.d.ts +3 -0
- package/src/interfaces/network/index.d.ts.map +1 -0
- package/src/interfaces/network/index.js +6 -0
- package/src/interfaces/network/index.js.map +1 -0
- package/src/interfaces/network/upnpService.d.ts +86 -0
- package/src/interfaces/network/upnpService.d.ts.map +1 -0
- package/src/interfaces/network/upnpService.js +3 -0
- package/src/interfaces/network/upnpService.js.map +1 -0
- package/src/interfaces/network/upnpTypes.d.ts +120 -0
- package/src/interfaces/network/upnpTypes.d.ts.map +1 -0
- package/src/interfaces/network/upnpTypes.js +57 -0
- package/src/interfaces/network/upnpTypes.js.map +1 -0
- package/src/plugins/index.d.ts +1 -0
- package/src/plugins/index.d.ts.map +1 -1
- package/src/plugins/index.js +1 -0
- package/src/plugins/index.js.map +1 -1
- package/src/plugins/upnp.d.ts +129 -0
- package/src/plugins/upnp.d.ts.map +1 -0
- package/src/plugins/upnp.js +158 -0
- package/src/plugins/upnp.js.map +1 -0
- package/src/services/index.d.ts +3 -0
- package/src/services/index.d.ts.map +1 -1
- package/src/services/index.js +3 -0
- package/src/services/index.js.map +1 -1
- package/src/services/upnp-config.d.ts +131 -0
- package/src/services/upnp-config.d.ts.map +1 -0
- package/src/services/upnp-config.js +225 -0
- package/src/services/upnp-config.js.map +1 -0
- package/src/services/upnp-manager.d.ts +211 -0
- package/src/services/upnp-manager.d.ts.map +1 -0
- package/src/services/upnp-manager.js +447 -0
- package/src/services/upnp-manager.js.map +1 -0
- package/src/services/upnp.d.ts +241 -0
- package/src/services/upnp.d.ts.map +1 -0
- package/src/services/upnp.js +415 -0
- package/src/services/upnp.js.map +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UPnP Manager for Express server integration.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates UPnP port mapping lifecycle: initialization, periodic refresh,
|
|
5
|
+
* health monitoring, and graceful shutdown. Wraps the core UpnpService with
|
|
6
|
+
* server-specific concerns like signal handling and exponential backoff on
|
|
7
|
+
* refresh failures.
|
|
8
|
+
*
|
|
9
|
+
* All UPnP failures are non-fatal — the server continues operating even if
|
|
10
|
+
* port mapping fails, with appropriate log messages for manual intervention.
|
|
11
|
+
*
|
|
12
|
+
* Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 4.10
|
|
13
|
+
*/
|
|
14
|
+
import { IUpnpConfig } from '../interfaces/network/upnpTypes';
|
|
15
|
+
import { UpnpConfig } from './upnp-config';
|
|
16
|
+
/**
|
|
17
|
+
* Options for constructing a UpnpManager.
|
|
18
|
+
*
|
|
19
|
+
* Allows passing a config along with an optional description prefix
|
|
20
|
+
* for port mapping labels.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const manager = new UpnpManager({
|
|
25
|
+
* config: UpnpConfig.fromEnvironment(),
|
|
26
|
+
* descriptionPrefix: 'My App',
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export interface UpnpManagerOptions {
|
|
31
|
+
/** UPnP configuration (IUpnpConfig or UpnpConfig instance) */
|
|
32
|
+
config: IUpnpConfig | UpnpConfig;
|
|
33
|
+
/** Prefix for port mapping descriptions (defaults to 'Express App') */
|
|
34
|
+
descriptionPrefix?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Manages UPnP port mappings for the Express server lifecycle.
|
|
38
|
+
*
|
|
39
|
+
* Handles:
|
|
40
|
+
* - Creating HTTP port mapping on startup
|
|
41
|
+
* - Creating WebSocket port mapping when on a separate port
|
|
42
|
+
* - Removing mappings on shutdown
|
|
43
|
+
* - Periodic health monitoring and refresh
|
|
44
|
+
* - SIGTERM/SIGINT signal handling
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const config = UpnpConfig.fromEnvironment();
|
|
49
|
+
* const manager = new UpnpManager(config);
|
|
50
|
+
*
|
|
51
|
+
* await manager.initialize(); // creates mapping, starts refresh timer
|
|
52
|
+
* // ... server runs ...
|
|
53
|
+
* await manager.shutdown(); // removes mappings, cleans up
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // With custom description prefix
|
|
59
|
+
* const manager = new UpnpManager({
|
|
60
|
+
* config: UpnpConfig.fromEnvironment(),
|
|
61
|
+
* descriptionPrefix: 'My App',
|
|
62
|
+
* });
|
|
63
|
+
* // Mappings will be labelled "My App HTTP" and "My App WebSocket"
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare class UpnpManager {
|
|
67
|
+
/** The underlying UPnP service */
|
|
68
|
+
private readonly service;
|
|
69
|
+
/** Server configuration */
|
|
70
|
+
private readonly config;
|
|
71
|
+
/** Prefix used in port mapping descriptions */
|
|
72
|
+
private readonly descriptionPrefix;
|
|
73
|
+
/** Periodic refresh timer handle */
|
|
74
|
+
private refreshTimer;
|
|
75
|
+
/** Consecutive refresh failure count (for exponential backoff) */
|
|
76
|
+
private consecutiveRefreshFailures;
|
|
77
|
+
/** Whether the manager has been initialized */
|
|
78
|
+
private initialized;
|
|
79
|
+
/** Whether shutdown is in progress or complete */
|
|
80
|
+
private shuttingDown;
|
|
81
|
+
/** Bound signal handlers (stored for removal on shutdown) */
|
|
82
|
+
private readonly boundSignalHandlers;
|
|
83
|
+
/**
|
|
84
|
+
* Create a new UpnpManager.
|
|
85
|
+
*
|
|
86
|
+
* Accepts either a config directly (backward compatible) or an options
|
|
87
|
+
* object with an optional `descriptionPrefix`.
|
|
88
|
+
*
|
|
89
|
+
* @param configOrOptions - UPnP configuration or options object
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* // Direct config (backward compatible)
|
|
94
|
+
* const manager = new UpnpManager(config);
|
|
95
|
+
*
|
|
96
|
+
* // Options object with custom prefix
|
|
97
|
+
* const manager = new UpnpManager({
|
|
98
|
+
* config,
|
|
99
|
+
* descriptionPrefix: 'My App',
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
constructor(configOrOptions: IUpnpConfig | UpnpConfig | UpnpManagerOptions);
|
|
104
|
+
/**
|
|
105
|
+
* Initialize UPnP: create HTTP port mapping and start the refresh timer.
|
|
106
|
+
*
|
|
107
|
+
* **Validates: Requirements 4.4**
|
|
108
|
+
*
|
|
109
|
+
* On failure, logs a warning with manual port-forwarding instructions
|
|
110
|
+
* but does NOT throw — UPnP failure is non-fatal.
|
|
111
|
+
*/
|
|
112
|
+
initialize(): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Shut down UPnP: stop refresh timer, remove all mappings, close service.
|
|
115
|
+
*
|
|
116
|
+
* **Validates: Requirements 4.5, 4.6**
|
|
117
|
+
*
|
|
118
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
119
|
+
*/
|
|
120
|
+
shutdown(): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Whether the manager has been successfully initialized.
|
|
123
|
+
*
|
|
124
|
+
* @returns `true` if {@link initialize} completed successfully
|
|
125
|
+
*/
|
|
126
|
+
get isInitialized(): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the manager is shutting down or has shut down.
|
|
129
|
+
*
|
|
130
|
+
* @returns `true` if {@link shutdown} has been called
|
|
131
|
+
*/
|
|
132
|
+
get isShuttingDown(): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Get the external endpoints for advertisement.
|
|
135
|
+
*
|
|
136
|
+
* Returns the external IP:port for HTTP and WebSocket endpoints.
|
|
137
|
+
*
|
|
138
|
+
* **Validates: Requirement 4.10**
|
|
139
|
+
*
|
|
140
|
+
* @returns External endpoints or `null` if UPnP is not initialized or shutting down
|
|
141
|
+
*/
|
|
142
|
+
getExternalEndpoints(): Promise<{
|
|
143
|
+
http: string;
|
|
144
|
+
ws: string;
|
|
145
|
+
} | null>;
|
|
146
|
+
/**
|
|
147
|
+
* Create the HTTP port mapping on the router.
|
|
148
|
+
*
|
|
149
|
+
* **Validates: Requirement 4.2** — Uses configurable description prefix
|
|
150
|
+
*/
|
|
151
|
+
private createHttpMapping;
|
|
152
|
+
/**
|
|
153
|
+
* Create the WebSocket port mapping on the router.
|
|
154
|
+
*
|
|
155
|
+
* Only called when websocketPort differs from httpPort.
|
|
156
|
+
*
|
|
157
|
+
* **Validates: Requirement 4.3** — Uses configurable description prefix
|
|
158
|
+
*/
|
|
159
|
+
private createWebSocketMapping;
|
|
160
|
+
/**
|
|
161
|
+
* Whether the WebSocket port requires a separate mapping.
|
|
162
|
+
*
|
|
163
|
+
* @returns `true` if websocketPort differs from httpPort
|
|
164
|
+
*/
|
|
165
|
+
private get needsWebSocketMapping();
|
|
166
|
+
/**
|
|
167
|
+
* Start the periodic refresh timer.
|
|
168
|
+
*
|
|
169
|
+
* **Validates: Requirement 4.8** — Refresh mappings before TTL expiration
|
|
170
|
+
*/
|
|
171
|
+
private startRefreshTimer;
|
|
172
|
+
/**
|
|
173
|
+
* Stop the periodic refresh timer.
|
|
174
|
+
*/
|
|
175
|
+
private stopRefreshTimer;
|
|
176
|
+
/**
|
|
177
|
+
* Refresh all active port mappings.
|
|
178
|
+
*
|
|
179
|
+
* **Validates: Requirement 4.8** — Re-create each active mapping to renew TTL
|
|
180
|
+
*/
|
|
181
|
+
private refresh;
|
|
182
|
+
/**
|
|
183
|
+
* Schedule an additional refresh with exponential backoff.
|
|
184
|
+
*
|
|
185
|
+
* **Validates: Requirement 4.9** — Exponential backoff capped at 8× retryDelay
|
|
186
|
+
*/
|
|
187
|
+
private scheduleBackoffRefresh;
|
|
188
|
+
/**
|
|
189
|
+
* Register SIGTERM and SIGINT handlers for graceful shutdown.
|
|
190
|
+
*
|
|
191
|
+
* **Validates: Requirement 4.4** — Register signal handlers during initialization
|
|
192
|
+
*/
|
|
193
|
+
private registerSignalHandlers;
|
|
194
|
+
/**
|
|
195
|
+
* Remove previously registered signal handlers.
|
|
196
|
+
*/
|
|
197
|
+
private removeSignalHandlers;
|
|
198
|
+
/**
|
|
199
|
+
* Handle a process signal by performing graceful shutdown.
|
|
200
|
+
*
|
|
201
|
+
* @param signal - The signal name (e.g. `'SIGTERM'`, `'SIGINT'`)
|
|
202
|
+
*/
|
|
203
|
+
private handleSignal;
|
|
204
|
+
/**
|
|
205
|
+
* Log manual port-forwarding instructions when UPnP is unavailable.
|
|
206
|
+
*
|
|
207
|
+
* **Validates: Requirement 4.7** — Log warning with manual instructions
|
|
208
|
+
*/
|
|
209
|
+
private logManualPortForwardingInstructions;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=upnp-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upnp-manager.d.ts","sourceRoot":"","sources":["../../../../../packages/digitaldefiance-node-express-suite/src/services/upnp-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,WAAW,EAGZ,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAe3C;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,MAAM,EAAE,WAAW,GAAG,UAAU,CAAC;IACjC,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAsBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,WAAW;IACtB,kCAAkC;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC,2BAA2B;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAE/C,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C,oCAAoC;IACpC,OAAO,CAAC,YAAY,CAA+C;IAEnE,kEAAkE;IAClE,OAAO,CAAC,0BAA0B,CAAK;IAEvC,+CAA+C;IAC/C,OAAO,CAAC,WAAW,CAAS;IAE5B,kDAAkD;IAClD,OAAO,CAAC,YAAY,CAAS;IAE7B,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAGlC;IAEF;;;;;;;;;;;;;;;;;;;OAmBG;gBACS,eAAe,EAAE,WAAW,GAAG,UAAU,GAAG,kBAAkB;IAkC1E;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoDjC;;;;;;OAMG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/B;;;;OAIG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;;OAIG;IACH,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED;;;;;;;;OAQG;IACG,oBAAoB,IAAI,OAAO,CAAC;QACpC,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;KACZ,GAAG,IAAI,CAAC;IAkBT;;;;OAIG;YACW,iBAAiB;IAY/B;;;;;;OAMG;YACW,sBAAsB;IAYpC;;;;OAIG;IACH,OAAO,KAAK,qBAAqB,GAEhC;IAID;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAwBzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAQxB;;;;OAIG;YACW,OAAO;IAsErB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAwB9B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAK9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;;;OAIG;YACW,YAAY;IAO1B;;;;OAIG;IACH,OAAO,CAAC,mCAAmC;CAgB5C"}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* UPnP Manager for Express server integration.
|
|
4
|
+
*
|
|
5
|
+
* Orchestrates UPnP port mapping lifecycle: initialization, periodic refresh,
|
|
6
|
+
* health monitoring, and graceful shutdown. Wraps the core UpnpService with
|
|
7
|
+
* server-specific concerns like signal handling and exponential backoff on
|
|
8
|
+
* refresh failures.
|
|
9
|
+
*
|
|
10
|
+
* All UPnP failures are non-fatal — the server continues operating even if
|
|
11
|
+
* port mapping fails, with appropriate log messages for manual intervention.
|
|
12
|
+
*
|
|
13
|
+
* Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 4.10
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.UpnpManager = void 0;
|
|
17
|
+
const upnp_1 = require("./upnp");
|
|
18
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
19
|
+
/** Maximum backoff multiplier for repeated refresh failures */
|
|
20
|
+
const MAX_BACKOFF_MULTIPLIER = 8;
|
|
21
|
+
/** Prefix for all UPnP log messages */
|
|
22
|
+
const LOG_PREFIX = '[UPnP]';
|
|
23
|
+
/** Default description prefix for port mapping labels */
|
|
24
|
+
const DEFAULT_DESCRIPTION_PREFIX = 'Express App';
|
|
25
|
+
// ─── Type Guard ─────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Check whether the given value is a UpnpManagerOptions object
|
|
28
|
+
* (as opposed to a plain IUpnpConfig or UpnpConfig).
|
|
29
|
+
*/
|
|
30
|
+
function isUpnpManagerOptions(value) {
|
|
31
|
+
return (typeof value === 'object' &&
|
|
32
|
+
value !== null &&
|
|
33
|
+
'config' in value &&
|
|
34
|
+
// UpnpConfig and IUpnpConfig both have 'enabled'; UpnpManagerOptions has 'config'
|
|
35
|
+
typeof value.config === 'object');
|
|
36
|
+
}
|
|
37
|
+
// ─── Manager ────────────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Manages UPnP port mappings for the Express server lifecycle.
|
|
40
|
+
*
|
|
41
|
+
* Handles:
|
|
42
|
+
* - Creating HTTP port mapping on startup
|
|
43
|
+
* - Creating WebSocket port mapping when on a separate port
|
|
44
|
+
* - Removing mappings on shutdown
|
|
45
|
+
* - Periodic health monitoring and refresh
|
|
46
|
+
* - SIGTERM/SIGINT signal handling
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const config = UpnpConfig.fromEnvironment();
|
|
51
|
+
* const manager = new UpnpManager(config);
|
|
52
|
+
*
|
|
53
|
+
* await manager.initialize(); // creates mapping, starts refresh timer
|
|
54
|
+
* // ... server runs ...
|
|
55
|
+
* await manager.shutdown(); // removes mappings, cleans up
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // With custom description prefix
|
|
61
|
+
* const manager = new UpnpManager({
|
|
62
|
+
* config: UpnpConfig.fromEnvironment(),
|
|
63
|
+
* descriptionPrefix: 'My App',
|
|
64
|
+
* });
|
|
65
|
+
* // Mappings will be labelled "My App HTTP" and "My App WebSocket"
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
class UpnpManager {
|
|
69
|
+
/** The underlying UPnP service */
|
|
70
|
+
service;
|
|
71
|
+
/** Server configuration */
|
|
72
|
+
config;
|
|
73
|
+
/** Prefix used in port mapping descriptions */
|
|
74
|
+
descriptionPrefix;
|
|
75
|
+
/** Periodic refresh timer handle */
|
|
76
|
+
refreshTimer = null;
|
|
77
|
+
/** Consecutive refresh failure count (for exponential backoff) */
|
|
78
|
+
consecutiveRefreshFailures = 0;
|
|
79
|
+
/** Whether the manager has been initialized */
|
|
80
|
+
initialized = false;
|
|
81
|
+
/** Whether shutdown is in progress or complete */
|
|
82
|
+
shuttingDown = false;
|
|
83
|
+
/** Bound signal handlers (stored for removal on shutdown) */
|
|
84
|
+
boundSignalHandlers;
|
|
85
|
+
/**
|
|
86
|
+
* Create a new UpnpManager.
|
|
87
|
+
*
|
|
88
|
+
* Accepts either a config directly (backward compatible) or an options
|
|
89
|
+
* object with an optional `descriptionPrefix`.
|
|
90
|
+
*
|
|
91
|
+
* @param configOrOptions - UPnP configuration or options object
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // Direct config (backward compatible)
|
|
96
|
+
* const manager = new UpnpManager(config);
|
|
97
|
+
*
|
|
98
|
+
* // Options object with custom prefix
|
|
99
|
+
* const manager = new UpnpManager({
|
|
100
|
+
* config,
|
|
101
|
+
* descriptionPrefix: 'My App',
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
constructor(configOrOptions) {
|
|
106
|
+
let resolvedConfig;
|
|
107
|
+
let prefix;
|
|
108
|
+
if (isUpnpManagerOptions(configOrOptions)) {
|
|
109
|
+
resolvedConfig = configOrOptions.config;
|
|
110
|
+
prefix = configOrOptions.descriptionPrefix ?? DEFAULT_DESCRIPTION_PREFIX;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
resolvedConfig = configOrOptions;
|
|
114
|
+
prefix = DEFAULT_DESCRIPTION_PREFIX;
|
|
115
|
+
}
|
|
116
|
+
this.config = resolvedConfig;
|
|
117
|
+
this.descriptionPrefix = prefix;
|
|
118
|
+
this.service = new upnp_1.UpnpService({
|
|
119
|
+
enabled: resolvedConfig.enabled,
|
|
120
|
+
httpPort: resolvedConfig.httpPort,
|
|
121
|
+
websocketPort: resolvedConfig.websocketPort,
|
|
122
|
+
ttl: resolvedConfig.ttl,
|
|
123
|
+
refreshInterval: resolvedConfig.refreshInterval,
|
|
124
|
+
protocol: resolvedConfig.protocol,
|
|
125
|
+
retryAttempts: resolvedConfig.retryAttempts,
|
|
126
|
+
retryDelay: resolvedConfig.retryDelay,
|
|
127
|
+
});
|
|
128
|
+
// Bind signal handlers so we can remove them later
|
|
129
|
+
this.boundSignalHandlers = {
|
|
130
|
+
sigterm: () => void this.handleSignal('SIGTERM'),
|
|
131
|
+
sigint: () => void this.handleSignal('SIGINT'),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// ─── Public API ─────────────────────────────────────────────────────────
|
|
135
|
+
/**
|
|
136
|
+
* Initialize UPnP: create HTTP port mapping and start the refresh timer.
|
|
137
|
+
*
|
|
138
|
+
* **Validates: Requirements 4.4**
|
|
139
|
+
*
|
|
140
|
+
* On failure, logs a warning with manual port-forwarding instructions
|
|
141
|
+
* but does NOT throw — UPnP failure is non-fatal.
|
|
142
|
+
*/
|
|
143
|
+
async initialize() {
|
|
144
|
+
if (this.initialized) {
|
|
145
|
+
console.warn(`${LOG_PREFIX} Already initialized, skipping`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.log(`${LOG_PREFIX} Initializing UPnP port mapping...`);
|
|
149
|
+
// Register signal handlers for graceful shutdown
|
|
150
|
+
this.registerSignalHandlers();
|
|
151
|
+
try {
|
|
152
|
+
// Discover external IP
|
|
153
|
+
const externalIp = await this.service.getExternalIp();
|
|
154
|
+
console.log(`${LOG_PREFIX} External IP: ${externalIp}`);
|
|
155
|
+
// Create HTTP port mapping
|
|
156
|
+
await this.createHttpMapping();
|
|
157
|
+
console.log(`${LOG_PREFIX} HTTP port mapping created — ` +
|
|
158
|
+
`external ${externalIp}:${this.config.httpPort} → ` +
|
|
159
|
+
`internal :${this.config.httpPort}`);
|
|
160
|
+
// Create WebSocket port mapping if on a different port
|
|
161
|
+
if (this.needsWebSocketMapping) {
|
|
162
|
+
await this.createWebSocketMapping();
|
|
163
|
+
console.log(`${LOG_PREFIX} WebSocket port mapping created — ` +
|
|
164
|
+
`external ${externalIp}:${this.config.websocketPort} → ` +
|
|
165
|
+
`internal :${this.config.websocketPort}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(`${LOG_PREFIX} WebSocket using same port as HTTP (${this.config.httpPort}), no additional mapping needed`);
|
|
169
|
+
}
|
|
170
|
+
// Start periodic refresh timer
|
|
171
|
+
this.startRefreshTimer();
|
|
172
|
+
this.initialized = true;
|
|
173
|
+
console.log(`${LOG_PREFIX} Initialization complete`);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
console.warn(`${LOG_PREFIX} Initialization failed: ${message}`);
|
|
178
|
+
this.logManualPortForwardingInstructions();
|
|
179
|
+
// Non-fatal: server continues without UPnP
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Shut down UPnP: stop refresh timer, remove all mappings, close service.
|
|
184
|
+
*
|
|
185
|
+
* **Validates: Requirements 4.5, 4.6**
|
|
186
|
+
*
|
|
187
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
188
|
+
*/
|
|
189
|
+
async shutdown() {
|
|
190
|
+
if (this.shuttingDown) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.shuttingDown = true;
|
|
194
|
+
console.log(`${LOG_PREFIX} Shutting down...`);
|
|
195
|
+
// Stop refresh timer
|
|
196
|
+
this.stopRefreshTimer();
|
|
197
|
+
// Remove signal handlers
|
|
198
|
+
this.removeSignalHandlers();
|
|
199
|
+
// Remove all mappings and close the service
|
|
200
|
+
try {
|
|
201
|
+
await this.service.close();
|
|
202
|
+
console.log(`${LOG_PREFIX} All mappings removed and service closed`);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
+
console.error(`${LOG_PREFIX} Error during shutdown: ${message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Whether the manager has been successfully initialized.
|
|
211
|
+
*
|
|
212
|
+
* @returns `true` if {@link initialize} completed successfully
|
|
213
|
+
*/
|
|
214
|
+
get isInitialized() {
|
|
215
|
+
return this.initialized;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Whether the manager is shutting down or has shut down.
|
|
219
|
+
*
|
|
220
|
+
* @returns `true` if {@link shutdown} has been called
|
|
221
|
+
*/
|
|
222
|
+
get isShuttingDown() {
|
|
223
|
+
return this.shuttingDown;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get the external endpoints for advertisement.
|
|
227
|
+
*
|
|
228
|
+
* Returns the external IP:port for HTTP and WebSocket endpoints.
|
|
229
|
+
*
|
|
230
|
+
* **Validates: Requirement 4.10**
|
|
231
|
+
*
|
|
232
|
+
* @returns External endpoints or `null` if UPnP is not initialized or shutting down
|
|
233
|
+
*/
|
|
234
|
+
async getExternalEndpoints() {
|
|
235
|
+
if (!this.initialized || this.shuttingDown) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const externalIp = await this.service.getExternalIp();
|
|
240
|
+
return {
|
|
241
|
+
http: `http://${externalIp}:${this.config.httpPort}`,
|
|
242
|
+
ws: `ws://${externalIp}:${this.config.websocketPort}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// ─── Private: Port Mapping ──────────────────────────────────────────────
|
|
250
|
+
/**
|
|
251
|
+
* Create the HTTP port mapping on the router.
|
|
252
|
+
*
|
|
253
|
+
* **Validates: Requirement 4.2** — Uses configurable description prefix
|
|
254
|
+
*/
|
|
255
|
+
async createHttpMapping() {
|
|
256
|
+
const mapping = {
|
|
257
|
+
public: this.config.httpPort,
|
|
258
|
+
private: this.config.httpPort,
|
|
259
|
+
protocol: 'tcp',
|
|
260
|
+
description: `${this.descriptionPrefix} HTTP`,
|
|
261
|
+
ttl: this.config.ttl,
|
|
262
|
+
};
|
|
263
|
+
await this.service.createPortMapping(mapping);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Create the WebSocket port mapping on the router.
|
|
267
|
+
*
|
|
268
|
+
* Only called when websocketPort differs from httpPort.
|
|
269
|
+
*
|
|
270
|
+
* **Validates: Requirement 4.3** — Uses configurable description prefix
|
|
271
|
+
*/
|
|
272
|
+
async createWebSocketMapping() {
|
|
273
|
+
const mapping = {
|
|
274
|
+
public: this.config.websocketPort,
|
|
275
|
+
private: this.config.websocketPort,
|
|
276
|
+
protocol: 'tcp',
|
|
277
|
+
description: `${this.descriptionPrefix} WebSocket`,
|
|
278
|
+
ttl: this.config.ttl,
|
|
279
|
+
};
|
|
280
|
+
await this.service.createPortMapping(mapping);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Whether the WebSocket port requires a separate mapping.
|
|
284
|
+
*
|
|
285
|
+
* @returns `true` if websocketPort differs from httpPort
|
|
286
|
+
*/
|
|
287
|
+
get needsWebSocketMapping() {
|
|
288
|
+
return this.config.websocketPort !== this.config.httpPort;
|
|
289
|
+
}
|
|
290
|
+
// ─── Private: Refresh ───────────────────────────────────────────────────
|
|
291
|
+
/**
|
|
292
|
+
* Start the periodic refresh timer.
|
|
293
|
+
*
|
|
294
|
+
* **Validates: Requirement 4.8** — Refresh mappings before TTL expiration
|
|
295
|
+
*/
|
|
296
|
+
startRefreshTimer() {
|
|
297
|
+
if (this.refreshTimer) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const intervalMs = this.config.refreshInterval;
|
|
301
|
+
console.log(`${LOG_PREFIX} Refresh timer started (interval: ${intervalMs}ms)`);
|
|
302
|
+
this.refreshTimer = setInterval(() => {
|
|
303
|
+
void this.refresh();
|
|
304
|
+
}, intervalMs);
|
|
305
|
+
// Allow the process to exit even if the timer is still running
|
|
306
|
+
if (this.refreshTimer &&
|
|
307
|
+
typeof this.refreshTimer === 'object' &&
|
|
308
|
+
'unref' in this.refreshTimer) {
|
|
309
|
+
this.refreshTimer.unref();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Stop the periodic refresh timer.
|
|
314
|
+
*/
|
|
315
|
+
stopRefreshTimer() {
|
|
316
|
+
if (this.refreshTimer) {
|
|
317
|
+
clearInterval(this.refreshTimer);
|
|
318
|
+
this.refreshTimer = null;
|
|
319
|
+
console.log(`${LOG_PREFIX} Refresh timer stopped`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Refresh all active port mappings.
|
|
324
|
+
*
|
|
325
|
+
* **Validates: Requirement 4.8** — Re-create each active mapping to renew TTL
|
|
326
|
+
*/
|
|
327
|
+
async refresh() {
|
|
328
|
+
if (this.shuttingDown) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
// Get current active mappings from the service
|
|
333
|
+
const activeMappings = await this.service.getMappings();
|
|
334
|
+
if (activeMappings.length === 0) {
|
|
335
|
+
// No active mappings — try to recreate
|
|
336
|
+
console.warn(`${LOG_PREFIX} No active mappings found during refresh, recreating...`);
|
|
337
|
+
await this.createHttpMapping();
|
|
338
|
+
if (this.needsWebSocketMapping) {
|
|
339
|
+
await this.createWebSocketMapping();
|
|
340
|
+
}
|
|
341
|
+
this.consecutiveRefreshFailures = 0;
|
|
342
|
+
console.log(`${LOG_PREFIX} Mapping(s) recreated successfully`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Refresh each active mapping by re-creating it
|
|
346
|
+
const errors = [];
|
|
347
|
+
for (const mapping of activeMappings) {
|
|
348
|
+
try {
|
|
349
|
+
await this.service.createPortMapping(mapping);
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
353
|
+
errors.push(`${mapping.public}/${mapping.protocol}: ${msg}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (errors.length > 0) {
|
|
357
|
+
console.warn(`${LOG_PREFIX} Partial refresh failure: ${errors.join('; ')}`);
|
|
358
|
+
this.consecutiveRefreshFailures++;
|
|
359
|
+
this.scheduleBackoffRefresh();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// Verify mappings still exist after refresh
|
|
363
|
+
const verifiedMappings = await this.service.getMappings();
|
|
364
|
+
if (verifiedMappings.length < activeMappings.length) {
|
|
365
|
+
console.warn(`${LOG_PREFIX} Mapping verification failed — ` +
|
|
366
|
+
`expected ${activeMappings.length}, found ${verifiedMappings.length}. Recreating...`);
|
|
367
|
+
await this.createHttpMapping();
|
|
368
|
+
if (this.needsWebSocketMapping) {
|
|
369
|
+
await this.createWebSocketMapping();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
this.consecutiveRefreshFailures = 0;
|
|
373
|
+
console.log(`${LOG_PREFIX} Refresh complete (${verifiedMappings.length} mapping(s) active)`);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
this.consecutiveRefreshFailures++;
|
|
377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
378
|
+
console.error(`${LOG_PREFIX} Refresh failed (attempt ${this.consecutiveRefreshFailures}): ${message}`);
|
|
379
|
+
this.scheduleBackoffRefresh();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Schedule an additional refresh with exponential backoff.
|
|
384
|
+
*
|
|
385
|
+
* **Validates: Requirement 4.9** — Exponential backoff capped at 8× retryDelay
|
|
386
|
+
*/
|
|
387
|
+
scheduleBackoffRefresh() {
|
|
388
|
+
const multiplier = Math.min(Math.pow(2, this.consecutiveRefreshFailures - 1), MAX_BACKOFF_MULTIPLIER);
|
|
389
|
+
const backoffMs = this.config.retryDelay * multiplier;
|
|
390
|
+
console.warn(`${LOG_PREFIX} Scheduling backoff refresh in ${backoffMs}ms ` +
|
|
391
|
+
`(failure #${this.consecutiveRefreshFailures})`);
|
|
392
|
+
const timer = setTimeout(() => {
|
|
393
|
+
void this.refresh();
|
|
394
|
+
}, backoffMs);
|
|
395
|
+
// Don't prevent process exit
|
|
396
|
+
if (timer && typeof timer === 'object' && 'unref' in timer) {
|
|
397
|
+
timer.unref();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// ─── Private: Signal Handling ───────────────────────────────────────────
|
|
401
|
+
/**
|
|
402
|
+
* Register SIGTERM and SIGINT handlers for graceful shutdown.
|
|
403
|
+
*
|
|
404
|
+
* **Validates: Requirement 4.4** — Register signal handlers during initialization
|
|
405
|
+
*/
|
|
406
|
+
registerSignalHandlers() {
|
|
407
|
+
process.on('SIGTERM', this.boundSignalHandlers.sigterm);
|
|
408
|
+
process.on('SIGINT', this.boundSignalHandlers.sigint);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Remove previously registered signal handlers.
|
|
412
|
+
*/
|
|
413
|
+
removeSignalHandlers() {
|
|
414
|
+
process.removeListener('SIGTERM', this.boundSignalHandlers.sigterm);
|
|
415
|
+
process.removeListener('SIGINT', this.boundSignalHandlers.sigint);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Handle a process signal by performing graceful shutdown.
|
|
419
|
+
*
|
|
420
|
+
* @param signal - The signal name (e.g. `'SIGTERM'`, `'SIGINT'`)
|
|
421
|
+
*/
|
|
422
|
+
async handleSignal(signal) {
|
|
423
|
+
console.log(`${LOG_PREFIX} Received ${signal}, shutting down UPnP...`);
|
|
424
|
+
await this.shutdown();
|
|
425
|
+
}
|
|
426
|
+
// ─── Private: Logging Helpers ───────────────────────────────────────────
|
|
427
|
+
/**
|
|
428
|
+
* Log manual port-forwarding instructions when UPnP is unavailable.
|
|
429
|
+
*
|
|
430
|
+
* **Validates: Requirement 4.7** — Log warning with manual instructions
|
|
431
|
+
*/
|
|
432
|
+
logManualPortForwardingInstructions() {
|
|
433
|
+
let instructions = `${LOG_PREFIX} UPnP not available. Manual port forwarding required:\n` +
|
|
434
|
+
` Forward external port ${this.config.httpPort} to internal port ${this.config.httpPort}\n` +
|
|
435
|
+
` Protocol: TCP\n` +
|
|
436
|
+
` Description: ${this.descriptionPrefix} HTTP`;
|
|
437
|
+
if (this.needsWebSocketMapping) {
|
|
438
|
+
instructions +=
|
|
439
|
+
`\n Forward external port ${this.config.websocketPort} to internal port ${this.config.websocketPort}\n` +
|
|
440
|
+
` Protocol: TCP\n` +
|
|
441
|
+
` Description: ${this.descriptionPrefix} WebSocket`;
|
|
442
|
+
}
|
|
443
|
+
console.warn(instructions);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
exports.UpnpManager = UpnpManager;
|
|
447
|
+
//# sourceMappingURL=upnp-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upnp-manager.js","sourceRoot":"","sources":["../../../../../packages/digitaldefiance-node-express-suite/src/services/upnp-manager.ts"],"names":[],"mappings":"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"}
|