@basmilius/apple-rtsp 0.9.18 → 0.10.0

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 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
- * Override to provide default headers for every request.
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
- * Override to transform incoming data before RTSP parsing (e.g. decryption).
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
- * Override to transform outgoing data after RTSP formatting (e.g. encryption).
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
@@ -1,7 +1,16 @@
1
- import { Connection, HTTP_TIMEOUT } from "@basmilius/apple-common";
1
+ import { Connection, ConnectionClosedError, ConnectionTimeoutError, HTTP_TIMEOUT, InvalidResponseError, TimeoutError } from "@basmilius/apple-common";
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,8 +26,20 @@ 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");
42
+ if (headerLength === -1) return null;
22
43
  const { headers, method, path } = parseRequestHeaders(buffer.subarray(0, headerLength));
23
44
  let contentLength = headers["Content-Length"] ? Number(headers["Content-Length"]) : 0;
24
45
  if (isNaN(contentLength)) contentLength = 0;
@@ -32,8 +53,20 @@ function parseRequest(buffer) {
32
53
  requestLength
33
54
  };
34
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
+ */
35
67
  function parseResponse(buffer) {
36
68
  const headerLength = buffer.indexOf("\r\n\r\n");
69
+ if (headerLength === -1) return null;
37
70
  const { headers, status, statusText } = parseResponseHeaders(buffer.subarray(0, headerLength));
38
71
  let contentLength = headers["Content-Length"] ? Number(headers["Content-Length"]) : 0;
39
72
  if (isNaN(contentLength)) contentLength = 0;
@@ -49,20 +82,40 @@ function parseResponse(buffer) {
49
82
  responseLength
50
83
  };
51
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
+ */
52
95
  function parseHeaders(lines) {
53
96
  const headers = {};
54
97
  for (let i = 0; i < lines.length; i++) {
55
98
  const colon = lines[i].indexOf(":");
56
99
  if (colon <= 0) continue;
57
- 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());
58
101
  headers[name] = lines[i].substring(colon + 1).trim();
59
102
  }
60
103
  return headers;
61
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
+ */
62
115
  function parseRequestHeaders(buffer) {
63
116
  const lines = buffer.toString("utf8").split("\r\n");
64
- const rawRequest = lines[0].match(/^(\S+)\s+(\S+)\s+RTSP\/1\.0$/);
65
- 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]}`);
66
119
  const method = rawRequest[1];
67
120
  const path = rawRequest[2];
68
121
  return {
@@ -71,6 +124,16 @@ function parseRequestHeaders(buffer) {
71
124
  path
72
125
  };
73
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
+ */
74
137
  function parseResponseHeaders(buffer) {
75
138
  const lines = buffer.toString("utf8").split("\r\n");
76
139
  const rawStatus = lines[0].match(/(HTTP|RTSP)\/[\d.]+\s+(\d+)\s+(.+)/);
@@ -86,36 +149,97 @@ function parseResponseHeaders(buffer) {
86
149
 
87
150
  //#endregion
88
151
  //#region src/client.ts
152
+ /** Maximum allowed buffer size (2 MB) before resetting to prevent memory exhaustion. */
89
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
+ */
90
165
  var RtspClient = class extends Connection {
166
+ /** Accumulates decrypted plaintext data waiting to be parsed as RTSP responses. */
91
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. */
92
171
  #cseq = 0;
172
+ /** Map of in-flight requests keyed by CSeq, awaiting their matching response. */
93
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
+ */
94
181
  constructor(context, address, port) {
95
182
  super(context, address, port);
96
- this.on("close", this.#onClose.bind(this));
97
- this.on("data", this.#onData.bind(this));
98
- this.on("error", this.#onError.bind(this));
99
- this.on("timeout", this.#onTimeout.bind(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);
100
191
  }
101
192
  /**
102
- * Override to provide default headers for every request.
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.
103
200
  */
104
201
  getDefaultHeaders() {
105
202
  return {};
106
203
  }
107
204
  /**
108
- * Override to transform incoming data before RTSP parsing (e.g. decryption).
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.
109
213
  */
110
214
  transformIncoming(data) {
111
215
  return data;
112
216
  }
113
217
  /**
114
- * Override to transform outgoing data after RTSP formatting (e.g. encryption).
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.
115
225
  */
116
226
  transformOutgoing(data) {
117
227
  return data;
118
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
+ */
119
243
  async exchange(method, path, options = {}) {
120
244
  const { contentType, headers: extraHeaders = {}, allowError = false, protocol = "RTSP/1.0", timeout = HTTP_TIMEOUT } = options;
121
245
  let { body } = options;
@@ -147,12 +271,12 @@ var RtspClient = class extends Connection {
147
271
  return new Promise((resolve, reject) => {
148
272
  const timer = setTimeout(() => {
149
273
  this.#requests.delete(cseq);
150
- reject(/* @__PURE__ */ new Error(`No response to CSeq ${cseq} (${path})`));
274
+ reject(new TimeoutError(`No response to CSeq ${cseq} (${path})`));
151
275
  }, timeout);
152
276
  this.#requests.set(cseq, {
153
277
  resolve: (response) => {
154
278
  clearTimeout(timer);
155
- if (!allowError && !response.ok) reject(/* @__PURE__ */ new Error(`RTSP error: ${response.status} ${response.statusText}`));
279
+ if (!allowError && !response.ok) reject(new InvalidResponseError(`RTSP error: ${response.status} ${response.statusText}`));
156
280
  else resolve(response);
157
281
  },
158
282
  reject: (error) => {
@@ -163,26 +287,53 @@ var RtspClient = class extends Connection {
163
287
  this.write(data);
164
288
  });
165
289
  }
166
- #onClose() {
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() {
167
297
  this.#buffer = Buffer.alloc(0);
298
+ this.#encryptedBuffer = Buffer.alloc(0);
168
299
  for (const [cseq, { reject }] of this.#requests) {
169
- reject(/* @__PURE__ */ new Error("Connection closed"));
300
+ reject(new ConnectionClosedError("Connection closed."));
170
301
  this.#requests.delete(cseq);
171
302
  }
172
- this.context.logger.net("[rtsp]", "#onClose()");
303
+ this.context.logger.net("[rtsp]", "onRtspClose()");
173
304
  }
174
- #onData(data) {
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) {
175
319
  try {
176
- this.#buffer = Buffer.concat([this.#buffer, data]);
177
- if (this.#buffer.byteLength > MAX_BUFFER_SIZE) {
178
- this.context.logger.error("[rtsp]", `Buffer exceeded max size (${this.#buffer.byteLength} bytes), resetting.`);
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);
179
324
  this.#buffer = Buffer.alloc(0);
180
- this.emit("error", /* @__PURE__ */ new Error("Buffer overflow: exceeded maximum buffer size"));
325
+ const err = /* @__PURE__ */ new Error("Buffer overflow: exceeded maximum buffer size");
326
+ for (const [cseq, { reject }] of this.#requests) {
327
+ reject(err);
328
+ this.#requests.delete(cseq);
329
+ }
330
+ this.emit("error", err);
181
331
  return;
182
332
  }
183
- const transformed = this.transformIncoming(this.#buffer);
333
+ const transformed = this.transformIncoming(this.#encryptedBuffer);
184
334
  if (transformed === false) return;
185
- this.#buffer = transformed;
335
+ this.#encryptedBuffer = Buffer.alloc(0);
336
+ this.#buffer = Buffer.concat([this.#buffer, transformed]);
186
337
  while (this.#buffer.byteLength > 0) {
187
338
  const result = parseResponse(this.#buffer);
188
339
  if (result === null) return;
@@ -196,24 +347,33 @@ var RtspClient = class extends Connection {
196
347
  } else this.context.logger.warn("[rtsp]", `Unexpected response for CSeq ${cseq}`);
197
348
  }
198
349
  } catch (err) {
199
- this.context.logger.error("[rtsp]", "#onData()", err);
350
+ this.context.logger.error("[rtsp]", "onRtspData()", err);
200
351
  this.emit("error", err);
201
352
  }
202
353
  }
203
- #onError(err) {
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) {
204
360
  for (const [cseq, { reject }] of this.#requests) {
205
361
  reject(err);
206
362
  this.#requests.delete(cseq);
207
363
  }
208
- this.context.logger.error("[rtsp]", "#onError()", err);
364
+ this.context.logger.error("[rtsp]", "onRtspError()", err);
209
365
  }
210
- #onTimeout() {
211
- const err = /* @__PURE__ */ new Error("Connection timed out");
366
+ /**
367
+ * Handles socket timeout events by rejecting all pending requests with a
368
+ * {@link ConnectionTimeoutError}.
369
+ */
370
+ onRtspTimeout() {
371
+ const err = new ConnectionTimeoutError();
212
372
  for (const [cseq, { reject }] of this.#requests) {
213
373
  reject(err);
214
374
  this.#requests.delete(cseq);
215
375
  }
216
- this.context.logger.net("[rtsp]", "#onTimeout()");
376
+ this.context.logger.net("[rtsp]", "onRtspTimeout()");
217
377
  }
218
378
  };
219
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.9.18",
4
+ "version": "0.10.0",
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.9.18",
46
- "@basmilius/apple-encoding": "0.9.18"
45
+ "@basmilius/apple-common": "0.10.0",
46
+ "@basmilius/apple-encoding": "0.10.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/bun": "^1.3.11",