@basmilius/apple-rtsp 0.9.19 → 0.10.1
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/dist/index.d.mts +146 -17
- package/dist/index.mjs +176 -23
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,54 +1,183 @@
|
|
|
1
1
|
import { Connection, Context } from "@basmilius/apple-common";
|
|
2
2
|
|
|
3
3
|
//#region src/encoding.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Supported RTSP/HTTP method verbs used in Apple protocol communication.
|
|
6
|
+
*/
|
|
4
7
|
type Method = "ANNOUNCE" | "FLUSH" | "GET" | "GET_PARAMETER" | "OPTIONS" | "POST" | "PUT" | "RECORD" | "SETUP" | "SET_PARAMETER" | "TEARDOWN";
|
|
8
|
+
/**
|
|
9
|
+
* Parsed RTSP request containing method, path, headers, body, and total byte length.
|
|
10
|
+
*/
|
|
5
11
|
type RtspRequest = {
|
|
6
|
-
readonly headers: Record<string, string>;
|
|
7
|
-
readonly method: Method;
|
|
8
|
-
readonly path: string;
|
|
9
|
-
readonly body: Buffer;
|
|
12
|
+
/** Parsed request headers with normalized (capitalized) names. */readonly headers: Record<string, string>; /** The RTSP/HTTP method verb. */
|
|
13
|
+
readonly method: Method; /** The request target path (e.g. `/info`, `/pair-setup`). */
|
|
14
|
+
readonly path: string; /** The request body as a raw buffer, empty if no body was present. */
|
|
15
|
+
readonly body: Buffer; /** Total number of bytes consumed from the input buffer for this request (headers + body). */
|
|
10
16
|
readonly requestLength: number;
|
|
11
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Parsed RTSP response wrapping a standard `Response` object alongside the total byte length consumed.
|
|
20
|
+
*/
|
|
12
21
|
type RtspResponse = {
|
|
13
|
-
readonly response: Response;
|
|
22
|
+
/** The parsed response as a standard web `Response` object. */readonly response: Response; /** Total number of bytes consumed from the input buffer for this response (headers + body). */
|
|
14
23
|
readonly responseLength: number;
|
|
15
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Options for building a serialized RTSP/HTTP response buffer.
|
|
27
|
+
*/
|
|
16
28
|
type BuildResponseOptions = {
|
|
17
|
-
readonly status: number;
|
|
18
|
-
readonly statusText: string;
|
|
19
|
-
readonly headers?: Record<string, string | number>;
|
|
20
|
-
readonly body?: Buffer;
|
|
29
|
+
/** HTTP status code (e.g. 200, 404). */readonly status: number; /** HTTP status reason phrase (e.g. "OK", "Not Found"). */
|
|
30
|
+
readonly statusText: string; /** Additional response headers. Content-Length is set automatically. */
|
|
31
|
+
readonly headers?: Record<string, string | number>; /** Optional response body. */
|
|
32
|
+
readonly body?: Buffer; /** Protocol version line prefix. Defaults to `'RTSP/1.0'`. */
|
|
21
33
|
readonly protocol?: "RTSP/1.0" | "HTTP/1.1";
|
|
22
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Builds a serialized RTSP/HTTP response buffer from the given options.
|
|
37
|
+
*
|
|
38
|
+
* Automatically sets the `Content-Length` header based on the body size.
|
|
39
|
+
* The resulting buffer is ready to be sent over a TCP socket.
|
|
40
|
+
*
|
|
41
|
+
* @param options - Response configuration including status, headers, and body.
|
|
42
|
+
* @returns A buffer containing the fully formatted response.
|
|
43
|
+
*/
|
|
23
44
|
declare function buildResponse(options: BuildResponseOptions): Buffer;
|
|
45
|
+
/**
|
|
46
|
+
* Attempts to parse an RTSP/HTTP request from the given buffer.
|
|
47
|
+
*
|
|
48
|
+
* Returns `null` if the buffer does not yet contain a complete request
|
|
49
|
+
* (i.e. headers are incomplete or the body has not fully arrived).
|
|
50
|
+
* This allows incremental parsing as TCP data arrives in chunks.
|
|
51
|
+
*
|
|
52
|
+
* @param buffer - The raw input buffer to parse from.
|
|
53
|
+
* @returns The parsed request, or `null` if the buffer is incomplete.
|
|
54
|
+
* @throws Error if the request line is malformed.
|
|
55
|
+
*/
|
|
24
56
|
declare function parseRequest(buffer: Buffer): RtspRequest | null;
|
|
57
|
+
/**
|
|
58
|
+
* Attempts to parse an RTSP/HTTP response from the given buffer.
|
|
59
|
+
*
|
|
60
|
+
* Returns `null` if the buffer does not yet contain a complete response
|
|
61
|
+
* (i.e. headers are incomplete or the body has not fully arrived).
|
|
62
|
+
* This allows incremental parsing as TCP data arrives in chunks.
|
|
63
|
+
*
|
|
64
|
+
* @param buffer - The raw input buffer to parse from.
|
|
65
|
+
* @returns The parsed response wrapped in an {@link RtspResponse}, or `null` if the buffer is incomplete.
|
|
66
|
+
* @throws Error if the status line is malformed.
|
|
67
|
+
*/
|
|
25
68
|
declare function parseResponse(buffer: Buffer): RtspResponse | null;
|
|
26
69
|
//#endregion
|
|
27
70
|
//#region src/client.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Options for configuring an RTSP/HTTP request-response exchange.
|
|
73
|
+
*/
|
|
28
74
|
type ExchangeOptions = {
|
|
29
|
-
contentType?: string;
|
|
30
|
-
headers?: Record<string, string>;
|
|
31
|
-
body?: Buffer | string | Record<string, unknown>;
|
|
32
|
-
allowError?: boolean;
|
|
33
|
-
protocol?: "RTSP/1.0" | "HTTP/1.1";
|
|
75
|
+
/** Explicit Content-Type header. Automatically set to `application/x-apple-binary-plist` when body is a plain object. */contentType?: string; /** Additional headers to include in the request. Merged after default headers. */
|
|
76
|
+
headers?: Record<string, string>; /** Request body. Plain objects are serialized as binary plist; strings and buffers are sent as-is. */
|
|
77
|
+
body?: Buffer | string | Record<string, unknown>; /** When `true`, non-OK responses resolve instead of rejecting with {@link InvalidResponseError}. */
|
|
78
|
+
allowError?: boolean; /** Protocol version for the request line. Defaults to `'RTSP/1.0'`. */
|
|
79
|
+
protocol?: "RTSP/1.0" | "HTTP/1.1"; /** Response timeout in milliseconds. Defaults to `HTTP_TIMEOUT`. */
|
|
34
80
|
timeout?: number;
|
|
35
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* RTSP client for Apple protocol communication over TCP.
|
|
84
|
+
*
|
|
85
|
+
* Extends {@link Connection} with RTSP-specific request/response handling, including
|
|
86
|
+
* CSeq-based request tracking, automatic body serialization (binary plist for objects),
|
|
87
|
+
* and support for encryption via overridable transform hooks. Maintains separate buffers
|
|
88
|
+
* for encrypted and decrypted data to prevent corruption during partial TCP delivery.
|
|
89
|
+
*
|
|
90
|
+
* Subclasses should override {@link transformIncoming} and {@link transformOutgoing} to
|
|
91
|
+
* add encryption/decryption, and {@link getDefaultHeaders} to inject per-request headers.
|
|
92
|
+
*/
|
|
36
93
|
declare class RtspClient extends Connection<{}> {
|
|
37
94
|
#private;
|
|
95
|
+
/**
|
|
96
|
+
* Creates a new RTSP client and binds internal event handlers.
|
|
97
|
+
*
|
|
98
|
+
* @param context - Device context providing logger and configuration.
|
|
99
|
+
* @param address - The target host address to connect to.
|
|
100
|
+
* @param port - The target TCP port to connect to.
|
|
101
|
+
*/
|
|
38
102
|
constructor(context: Context, address: string, port: number);
|
|
39
103
|
/**
|
|
40
|
-
*
|
|
104
|
+
* Returns default headers that are included in every outgoing request.
|
|
105
|
+
*
|
|
106
|
+
* Override in subclasses to inject session-specific headers (e.g. DACP-ID,
|
|
107
|
+
* Active-Remote). These headers are placed after CSeq but before any
|
|
108
|
+
* per-request headers from {@link ExchangeOptions.headers}.
|
|
109
|
+
*
|
|
110
|
+
* @returns A record of header name-value pairs.
|
|
41
111
|
*/
|
|
42
112
|
protected getDefaultHeaders(): Record<string, string | number>;
|
|
43
113
|
/**
|
|
44
|
-
*
|
|
114
|
+
* Transforms incoming raw TCP data before RTSP response parsing.
|
|
115
|
+
*
|
|
116
|
+
* Override in subclasses to perform decryption. Return `false` if the buffer
|
|
117
|
+
* does not yet contain enough data for a complete decryption block, signaling
|
|
118
|
+
* that more data should be accumulated before retrying.
|
|
119
|
+
*
|
|
120
|
+
* @param data - The raw (potentially encrypted) incoming data buffer.
|
|
121
|
+
* @returns The transformed (decrypted) buffer, or `false` if more data is needed.
|
|
45
122
|
*/
|
|
46
123
|
protected transformIncoming(data: Buffer): Buffer | false;
|
|
47
124
|
/**
|
|
48
|
-
*
|
|
125
|
+
* Transforms outgoing data after RTSP request formatting.
|
|
126
|
+
*
|
|
127
|
+
* Override in subclasses to perform encryption on the fully formatted
|
|
128
|
+
* request buffer before it is written to the socket.
|
|
129
|
+
*
|
|
130
|
+
* @param data - The fully formatted RTSP request buffer (headers + body).
|
|
131
|
+
* @returns The transformed (encrypted) buffer ready for transmission.
|
|
49
132
|
*/
|
|
50
133
|
protected transformOutgoing(data: Buffer): Buffer;
|
|
134
|
+
/**
|
|
135
|
+
* Sends an RTSP/HTTP request and waits for the matching response.
|
|
136
|
+
*
|
|
137
|
+
* Automatically assigns a CSeq header, serializes the body (plain objects become
|
|
138
|
+
* binary plist), applies outgoing transformation (e.g. encryption), and tracks
|
|
139
|
+
* the pending response via a timeout-guarded promise.
|
|
140
|
+
*
|
|
141
|
+
* @param method - The RTSP/HTTP method verb.
|
|
142
|
+
* @param path - The request target path.
|
|
143
|
+
* @param options - Additional request configuration.
|
|
144
|
+
* @returns The response from the remote device.
|
|
145
|
+
* @throws TimeoutError if no response is received within the configured timeout.
|
|
146
|
+
* @throws InvalidResponseError if the response has a non-OK status and `allowError` is not set.
|
|
147
|
+
*/
|
|
51
148
|
protected exchange(method: Method, path: string, options?: ExchangeOptions): Promise<Response>;
|
|
149
|
+
/**
|
|
150
|
+
* Handles TCP connection close events.
|
|
151
|
+
*
|
|
152
|
+
* Resets both internal buffers and rejects all pending requests with a
|
|
153
|
+
* {@link ConnectionClosedError}.
|
|
154
|
+
*/
|
|
155
|
+
onRtspClose(): void;
|
|
156
|
+
/**
|
|
157
|
+
* Handles incoming TCP data by accumulating, transforming, and parsing RTSP responses.
|
|
158
|
+
*
|
|
159
|
+
* Raw data is first appended to the encrypted buffer, then passed through
|
|
160
|
+
* {@link transformIncoming} for decryption. The resulting plaintext is appended
|
|
161
|
+
* to the parse buffer and consumed as complete RTSP responses. Each response is
|
|
162
|
+
* matched to its pending request via the CSeq header.
|
|
163
|
+
*
|
|
164
|
+
* If the buffer exceeds {@link MAX_BUFFER_SIZE}, all buffers are reset and
|
|
165
|
+
* pending requests are rejected to prevent memory exhaustion.
|
|
166
|
+
*
|
|
167
|
+
* @param data - Raw TCP data received from the socket.
|
|
168
|
+
*/
|
|
169
|
+
onRtspData(data: Buffer): void;
|
|
170
|
+
/**
|
|
171
|
+
* Handles socket error events by rejecting all pending requests with the error.
|
|
172
|
+
*
|
|
173
|
+
* @param err - The error that occurred on the socket.
|
|
174
|
+
*/
|
|
175
|
+
onRtspError(err: Error): void;
|
|
176
|
+
/**
|
|
177
|
+
* Handles socket timeout events by rejecting all pending requests with a
|
|
178
|
+
* {@link ConnectionTimeoutError}.
|
|
179
|
+
*/
|
|
180
|
+
onRtspTimeout(): void;
|
|
52
181
|
}
|
|
53
182
|
//#endregion
|
|
54
183
|
export { type BuildResponseOptions, type ExchangeOptions, type Method, RtspClient, type RtspRequest, type RtspResponse, buildResponse, parseRequest, parseResponse };
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,15 @@ import { Connection, ConnectionClosedError, ConnectionTimeoutError, HTTP_TIMEOUT
|
|
|
2
2
|
import { Plist } from "@basmilius/apple-encoding";
|
|
3
3
|
|
|
4
4
|
//#region src/encoding.ts
|
|
5
|
+
/**
|
|
6
|
+
* Builds a serialized RTSP/HTTP response buffer from the given options.
|
|
7
|
+
*
|
|
8
|
+
* Automatically sets the `Content-Length` header based on the body size.
|
|
9
|
+
* The resulting buffer is ready to be sent over a TCP socket.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Response configuration including status, headers, and body.
|
|
12
|
+
* @returns A buffer containing the fully formatted response.
|
|
13
|
+
*/
|
|
5
14
|
function buildResponse(options) {
|
|
6
15
|
const { status, statusText, headers: extraHeaders = {}, body, protocol = "RTSP/1.0" } = options;
|
|
7
16
|
const headers = {
|
|
@@ -17,6 +26,17 @@ function buildResponse(options) {
|
|
|
17
26
|
if (body && body.byteLength > 0) return Buffer.concat([Buffer.from(headerLines), body]);
|
|
18
27
|
return Buffer.from(headerLines);
|
|
19
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Attempts to parse an RTSP/HTTP request from the given buffer.
|
|
31
|
+
*
|
|
32
|
+
* Returns `null` if the buffer does not yet contain a complete request
|
|
33
|
+
* (i.e. headers are incomplete or the body has not fully arrived).
|
|
34
|
+
* This allows incremental parsing as TCP data arrives in chunks.
|
|
35
|
+
*
|
|
36
|
+
* @param buffer - The raw input buffer to parse from.
|
|
37
|
+
* @returns The parsed request, or `null` if the buffer is incomplete.
|
|
38
|
+
* @throws Error if the request line is malformed.
|
|
39
|
+
*/
|
|
20
40
|
function parseRequest(buffer) {
|
|
21
41
|
const headerLength = buffer.indexOf("\r\n\r\n");
|
|
22
42
|
if (headerLength === -1) return null;
|
|
@@ -33,6 +53,17 @@ function parseRequest(buffer) {
|
|
|
33
53
|
requestLength
|
|
34
54
|
};
|
|
35
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Attempts to parse an RTSP/HTTP response from the given buffer.
|
|
58
|
+
*
|
|
59
|
+
* Returns `null` if the buffer does not yet contain a complete response
|
|
60
|
+
* (i.e. headers are incomplete or the body has not fully arrived).
|
|
61
|
+
* This allows incremental parsing as TCP data arrives in chunks.
|
|
62
|
+
*
|
|
63
|
+
* @param buffer - The raw input buffer to parse from.
|
|
64
|
+
* @returns The parsed response wrapped in an {@link RtspResponse}, or `null` if the buffer is incomplete.
|
|
65
|
+
* @throws Error if the status line is malformed.
|
|
66
|
+
*/
|
|
36
67
|
function parseResponse(buffer) {
|
|
37
68
|
const headerLength = buffer.indexOf("\r\n\r\n");
|
|
38
69
|
if (headerLength === -1) return null;
|
|
@@ -51,20 +82,40 @@ function parseResponse(buffer) {
|
|
|
51
82
|
responseLength
|
|
52
83
|
};
|
|
53
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Parses raw header lines into a key-value record.
|
|
87
|
+
*
|
|
88
|
+
* Header names are normalized to capitalized form (e.g. `content-length` becomes
|
|
89
|
+
* `Content-Length`) so that lookups work regardless of the sender's casing.
|
|
90
|
+
* Lines without a valid colon separator are silently skipped.
|
|
91
|
+
*
|
|
92
|
+
* @param lines - Individual header lines (without the request/status line).
|
|
93
|
+
* @returns A record mapping normalized header names to their values.
|
|
94
|
+
*/
|
|
54
95
|
function parseHeaders(lines) {
|
|
55
96
|
const headers = {};
|
|
56
97
|
for (let i = 0; i < lines.length; i++) {
|
|
57
98
|
const colon = lines[i].indexOf(":");
|
|
58
99
|
if (colon <= 0) continue;
|
|
59
|
-
const name = lines[i].substring(0, colon).trim();
|
|
100
|
+
const name = lines[i].substring(0, colon).trim().replace(/(^|-)(\w)/g, (_, prefix, char) => prefix + char.toUpperCase());
|
|
60
101
|
headers[name] = lines[i].substring(colon + 1).trim();
|
|
61
102
|
}
|
|
62
103
|
return headers;
|
|
63
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Parses the header section of an RTSP/HTTP request buffer into structured components.
|
|
107
|
+
*
|
|
108
|
+
* Extracts the method, path, and headers from the raw header bytes. The first line
|
|
109
|
+
* is expected to match the format `METHOD /path RTSP/1.0` or `METHOD /path HTTP/1.1`.
|
|
110
|
+
*
|
|
111
|
+
* @param buffer - The raw header bytes (up to but not including the `\r\n\r\n` delimiter).
|
|
112
|
+
* @returns An object containing the parsed headers, method, and path.
|
|
113
|
+
* @throws Error if the request line does not match the expected format.
|
|
114
|
+
*/
|
|
64
115
|
function parseRequestHeaders(buffer) {
|
|
65
116
|
const lines = buffer.toString("utf8").split("\r\n");
|
|
66
|
-
const rawRequest = lines[0].match(/^(\S+)\s+(\S+)\s+RTSP\/
|
|
67
|
-
if (!rawRequest) throw new Error(`Invalid RTSP request line: ${lines[0]}`);
|
|
117
|
+
const rawRequest = lines[0].match(/^(\S+)\s+(\S+)\s+(?:RTSP|HTTP)\/[\d.]+$/);
|
|
118
|
+
if (!rawRequest) throw new Error(`Invalid RTSP/HTTP request line: ${lines[0]}`);
|
|
68
119
|
const method = rawRequest[1];
|
|
69
120
|
const path = rawRequest[2];
|
|
70
121
|
return {
|
|
@@ -73,6 +124,16 @@ function parseRequestHeaders(buffer) {
|
|
|
73
124
|
path
|
|
74
125
|
};
|
|
75
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Parses the header section of an RTSP/HTTP response buffer into structured components.
|
|
129
|
+
*
|
|
130
|
+
* Extracts the status code, status text, and headers from the raw header bytes. The first
|
|
131
|
+
* line is expected to match the format `RTSP/1.0 200 OK` or `HTTP/1.1 200 OK`.
|
|
132
|
+
*
|
|
133
|
+
* @param buffer - The raw header bytes (up to but not including the `\r\n\r\n` delimiter).
|
|
134
|
+
* @returns An object containing the parsed headers, status code, and status text.
|
|
135
|
+
* @throws Error if the status line does not match the expected format.
|
|
136
|
+
*/
|
|
76
137
|
function parseResponseHeaders(buffer) {
|
|
77
138
|
const lines = buffer.toString("utf8").split("\r\n");
|
|
78
139
|
const rawStatus = lines[0].match(/(HTTP|RTSP)\/[\d.]+\s+(\d+)\s+(.+)/);
|
|
@@ -88,36 +149,97 @@ function parseResponseHeaders(buffer) {
|
|
|
88
149
|
|
|
89
150
|
//#endregion
|
|
90
151
|
//#region src/client.ts
|
|
152
|
+
/** Maximum allowed buffer size (2 MB) before resetting to prevent memory exhaustion. */
|
|
91
153
|
const MAX_BUFFER_SIZE = 2 * 1024 * 1024;
|
|
154
|
+
/**
|
|
155
|
+
* RTSP client for Apple protocol communication over TCP.
|
|
156
|
+
*
|
|
157
|
+
* Extends {@link Connection} with RTSP-specific request/response handling, including
|
|
158
|
+
* CSeq-based request tracking, automatic body serialization (binary plist for objects),
|
|
159
|
+
* and support for encryption via overridable transform hooks. Maintains separate buffers
|
|
160
|
+
* for encrypted and decrypted data to prevent corruption during partial TCP delivery.
|
|
161
|
+
*
|
|
162
|
+
* Subclasses should override {@link transformIncoming} and {@link transformOutgoing} to
|
|
163
|
+
* add encryption/decryption, and {@link getDefaultHeaders} to inject per-request headers.
|
|
164
|
+
*/
|
|
92
165
|
var RtspClient = class extends Connection {
|
|
166
|
+
/** Accumulates decrypted plaintext data waiting to be parsed as RTSP responses. */
|
|
93
167
|
#buffer = Buffer.alloc(0);
|
|
168
|
+
/** Accumulates raw encrypted TCP data before transformation/decryption. */
|
|
169
|
+
#encryptedBuffer = Buffer.alloc(0);
|
|
170
|
+
/** Monotonically increasing RTSP CSeq counter for request tracking. */
|
|
94
171
|
#cseq = 0;
|
|
172
|
+
/** Map of in-flight requests keyed by CSeq, awaiting their matching response. */
|
|
95
173
|
#requests = /* @__PURE__ */ new Map();
|
|
174
|
+
/**
|
|
175
|
+
* Creates a new RTSP client and binds internal event handlers.
|
|
176
|
+
*
|
|
177
|
+
* @param context - Device context providing logger and configuration.
|
|
178
|
+
* @param address - The target host address to connect to.
|
|
179
|
+
* @param port - The target TCP port to connect to.
|
|
180
|
+
*/
|
|
96
181
|
constructor(context, address, port) {
|
|
97
182
|
super(context, address, port);
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
100
|
-
this.
|
|
101
|
-
this.
|
|
183
|
+
this.onRtspClose = this.onRtspClose.bind(this);
|
|
184
|
+
this.onRtspData = this.onRtspData.bind(this);
|
|
185
|
+
this.onRtspError = this.onRtspError.bind(this);
|
|
186
|
+
this.onRtspTimeout = this.onRtspTimeout.bind(this);
|
|
187
|
+
this.on("close", this.onRtspClose);
|
|
188
|
+
this.on("data", this.onRtspData);
|
|
189
|
+
this.on("error", this.onRtspError);
|
|
190
|
+
this.on("timeout", this.onRtspTimeout);
|
|
102
191
|
}
|
|
103
192
|
/**
|
|
104
|
-
*
|
|
193
|
+
* Returns default headers that are included in every outgoing request.
|
|
194
|
+
*
|
|
195
|
+
* Override in subclasses to inject session-specific headers (e.g. DACP-ID,
|
|
196
|
+
* Active-Remote). These headers are placed after CSeq but before any
|
|
197
|
+
* per-request headers from {@link ExchangeOptions.headers}.
|
|
198
|
+
*
|
|
199
|
+
* @returns A record of header name-value pairs.
|
|
105
200
|
*/
|
|
106
201
|
getDefaultHeaders() {
|
|
107
202
|
return {};
|
|
108
203
|
}
|
|
109
204
|
/**
|
|
110
|
-
*
|
|
205
|
+
* Transforms incoming raw TCP data before RTSP response parsing.
|
|
206
|
+
*
|
|
207
|
+
* Override in subclasses to perform decryption. Return `false` if the buffer
|
|
208
|
+
* does not yet contain enough data for a complete decryption block, signaling
|
|
209
|
+
* that more data should be accumulated before retrying.
|
|
210
|
+
*
|
|
211
|
+
* @param data - The raw (potentially encrypted) incoming data buffer.
|
|
212
|
+
* @returns The transformed (decrypted) buffer, or `false` if more data is needed.
|
|
111
213
|
*/
|
|
112
214
|
transformIncoming(data) {
|
|
113
215
|
return data;
|
|
114
216
|
}
|
|
115
217
|
/**
|
|
116
|
-
*
|
|
218
|
+
* Transforms outgoing data after RTSP request formatting.
|
|
219
|
+
*
|
|
220
|
+
* Override in subclasses to perform encryption on the fully formatted
|
|
221
|
+
* request buffer before it is written to the socket.
|
|
222
|
+
*
|
|
223
|
+
* @param data - The fully formatted RTSP request buffer (headers + body).
|
|
224
|
+
* @returns The transformed (encrypted) buffer ready for transmission.
|
|
117
225
|
*/
|
|
118
226
|
transformOutgoing(data) {
|
|
119
227
|
return data;
|
|
120
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Sends an RTSP/HTTP request and waits for the matching response.
|
|
231
|
+
*
|
|
232
|
+
* Automatically assigns a CSeq header, serializes the body (plain objects become
|
|
233
|
+
* binary plist), applies outgoing transformation (e.g. encryption), and tracks
|
|
234
|
+
* the pending response via a timeout-guarded promise.
|
|
235
|
+
*
|
|
236
|
+
* @param method - The RTSP/HTTP method verb.
|
|
237
|
+
* @param path - The request target path.
|
|
238
|
+
* @param options - Additional request configuration.
|
|
239
|
+
* @returns The response from the remote device.
|
|
240
|
+
* @throws TimeoutError if no response is received within the configured timeout.
|
|
241
|
+
* @throws InvalidResponseError if the response has a non-OK status and `allowError` is not set.
|
|
242
|
+
*/
|
|
121
243
|
async exchange(method, path, options = {}) {
|
|
122
244
|
const { contentType, headers: extraHeaders = {}, allowError = false, protocol = "RTSP/1.0", timeout = HTTP_TIMEOUT } = options;
|
|
123
245
|
let { body } = options;
|
|
@@ -165,19 +287,40 @@ var RtspClient = class extends Connection {
|
|
|
165
287
|
this.write(data);
|
|
166
288
|
});
|
|
167
289
|
}
|
|
168
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Handles TCP connection close events.
|
|
292
|
+
*
|
|
293
|
+
* Resets both internal buffers and rejects all pending requests with a
|
|
294
|
+
* {@link ConnectionClosedError}.
|
|
295
|
+
*/
|
|
296
|
+
onRtspClose() {
|
|
169
297
|
this.#buffer = Buffer.alloc(0);
|
|
298
|
+
this.#encryptedBuffer = Buffer.alloc(0);
|
|
170
299
|
for (const [cseq, { reject }] of this.#requests) {
|
|
171
300
|
reject(new ConnectionClosedError("Connection closed."));
|
|
172
301
|
this.#requests.delete(cseq);
|
|
173
302
|
}
|
|
174
|
-
this.context.logger.net("[rtsp]", "
|
|
303
|
+
this.context.logger.net("[rtsp]", "onRtspClose()");
|
|
175
304
|
}
|
|
176
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Handles incoming TCP data by accumulating, transforming, and parsing RTSP responses.
|
|
307
|
+
*
|
|
308
|
+
* Raw data is first appended to the encrypted buffer, then passed through
|
|
309
|
+
* {@link transformIncoming} for decryption. The resulting plaintext is appended
|
|
310
|
+
* to the parse buffer and consumed as complete RTSP responses. Each response is
|
|
311
|
+
* matched to its pending request via the CSeq header.
|
|
312
|
+
*
|
|
313
|
+
* If the buffer exceeds {@link MAX_BUFFER_SIZE}, all buffers are reset and
|
|
314
|
+
* pending requests are rejected to prevent memory exhaustion.
|
|
315
|
+
*
|
|
316
|
+
* @param data - Raw TCP data received from the socket.
|
|
317
|
+
*/
|
|
318
|
+
onRtspData(data) {
|
|
177
319
|
try {
|
|
178
|
-
this.#
|
|
179
|
-
if (this.#
|
|
180
|
-
this.context.logger.error("[rtsp]", `Buffer exceeded max size (${this.#
|
|
320
|
+
this.#encryptedBuffer = Buffer.concat([this.#encryptedBuffer, data]);
|
|
321
|
+
if (this.#encryptedBuffer.byteLength > MAX_BUFFER_SIZE) {
|
|
322
|
+
this.context.logger.error("[rtsp]", `Buffer exceeded max size (${this.#encryptedBuffer.byteLength} bytes), resetting.`);
|
|
323
|
+
this.#encryptedBuffer = Buffer.alloc(0);
|
|
181
324
|
this.#buffer = Buffer.alloc(0);
|
|
182
325
|
const err = /* @__PURE__ */ new Error("Buffer overflow: exceeded maximum buffer size");
|
|
183
326
|
for (const [cseq, { reject }] of this.#requests) {
|
|
@@ -187,9 +330,10 @@ var RtspClient = class extends Connection {
|
|
|
187
330
|
this.emit("error", err);
|
|
188
331
|
return;
|
|
189
332
|
}
|
|
190
|
-
const transformed = this.transformIncoming(this.#
|
|
333
|
+
const transformed = this.transformIncoming(this.#encryptedBuffer);
|
|
191
334
|
if (transformed === false) return;
|
|
192
|
-
this.#
|
|
335
|
+
this.#encryptedBuffer = Buffer.alloc(0);
|
|
336
|
+
this.#buffer = Buffer.concat([this.#buffer, transformed]);
|
|
193
337
|
while (this.#buffer.byteLength > 0) {
|
|
194
338
|
const result = parseResponse(this.#buffer);
|
|
195
339
|
if (result === null) return;
|
|
@@ -203,24 +347,33 @@ var RtspClient = class extends Connection {
|
|
|
203
347
|
} else this.context.logger.warn("[rtsp]", `Unexpected response for CSeq ${cseq}`);
|
|
204
348
|
}
|
|
205
349
|
} catch (err) {
|
|
206
|
-
this.context.logger.error("[rtsp]", "
|
|
350
|
+
this.context.logger.error("[rtsp]", "onRtspData()", err);
|
|
207
351
|
this.emit("error", err);
|
|
208
352
|
}
|
|
209
353
|
}
|
|
210
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Handles socket error events by rejecting all pending requests with the error.
|
|
356
|
+
*
|
|
357
|
+
* @param err - The error that occurred on the socket.
|
|
358
|
+
*/
|
|
359
|
+
onRtspError(err) {
|
|
211
360
|
for (const [cseq, { reject }] of this.#requests) {
|
|
212
361
|
reject(err);
|
|
213
362
|
this.#requests.delete(cseq);
|
|
214
363
|
}
|
|
215
|
-
this.context.logger.error("[rtsp]", "
|
|
364
|
+
this.context.logger.error("[rtsp]", "onRtspError()", err);
|
|
216
365
|
}
|
|
217
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Handles socket timeout events by rejecting all pending requests with a
|
|
368
|
+
* {@link ConnectionTimeoutError}.
|
|
369
|
+
*/
|
|
370
|
+
onRtspTimeout() {
|
|
218
371
|
const err = new ConnectionTimeoutError();
|
|
219
372
|
for (const [cseq, { reject }] of this.#requests) {
|
|
220
373
|
reject(err);
|
|
221
374
|
this.#requests.delete(cseq);
|
|
222
375
|
}
|
|
223
|
-
this.context.logger.net("[rtsp]", "
|
|
376
|
+
this.context.logger.net("[rtsp]", "onRtspTimeout()");
|
|
224
377
|
}
|
|
225
378
|
};
|
|
226
379
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basmilius/apple-rtsp",
|
|
3
3
|
"description": "RTSP protocol implementation for Apple Protocols.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": {
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@basmilius/apple-common": "0.
|
|
46
|
-
"@basmilius/apple-encoding": "0.
|
|
45
|
+
"@basmilius/apple-common": "0.10.1",
|
|
46
|
+
"@basmilius/apple-encoding": "0.10.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/bun": "^1.3.11",
|