@basmilius/apple-airplay 0.9.16 → 0.9.18

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,34 +1,26 @@
1
1
  import { AccessoryCredentials, AccessoryKeys, AccessoryPair, AccessoryVerify, AudioSource, Context, DiscoveryResult, EncryptionAwareConnection, EventMap, TimingServer } from "@basmilius/apple-common";
2
+ import { RtspClient } from "@basmilius/apple-rtsp";
2
3
 
3
- //#region src/baseStream.d.ts
4
- type DefaultEventMap = {
5
- close: [];
6
- connect: [];
7
- error: [Error];
8
- timeout: [];
9
- };
10
- declare class BaseStream<TEventMap extends EventMap = {}> extends EncryptionAwareConnection<DefaultEventMap & TEventMap> {
11
- #private;
12
- decrypt(data: Buffer): Buffer | false;
13
- encrypt(data: Buffer): Buffer;
14
- }
15
- //#endregion
16
4
  //#region src/controlStream.d.ts
17
- declare class ControlStream extends BaseStream {
5
+ declare class ControlStream extends RtspClient {
18
6
  #private;
19
7
  get activeRemoteId(): string;
20
8
  get dacpId(): string;
21
9
  get sessionId(): string;
10
+ get isEncrypted(): boolean;
22
11
  constructor(context: Context, address: string, port: number);
23
- disconnect(): Promise<void>;
12
+ enableEncryption(readKey: Buffer, writeKey: Buffer): void;
13
+ protected getDefaultHeaders(): Record<string, string | number>;
14
+ protected transformIncoming(data: Buffer): Buffer | false;
15
+ protected transformOutgoing(data: Buffer): Buffer;
24
16
  flush(uri: string, headers: Record<string, string>): Promise<Response>;
25
- get(path: string, headers?: HeadersInit, timeout?: number): Promise<Response>;
26
- post(path: string, body?: Buffer | string | null, headers?: HeadersInit, timeout?: number): Promise<Response>;
27
- put(path: string, body?: Buffer | string | null, headers?: HeadersInit, timeout?: number): Promise<Response>;
28
- record(path: string, headers?: HeadersInit, timeout?: number): Promise<Response>;
29
- setup(path: string, body?: Buffer | string | null, headers?: HeadersInit, timeout?: number): Promise<Response>;
17
+ get(path: string, headers?: Record<string, string>, timeout?: number): Promise<Response>;
18
+ post(path: string, body?: Buffer | string | Record<string, unknown>, headers?: Record<string, string>, timeout?: number): Promise<Response>;
19
+ put(path: string, body?: Buffer | string | Record<string, unknown>, headers?: Record<string, string>, timeout?: number): Promise<Response>;
20
+ record(path: string, headers?: Record<string, string>, timeout?: number): Promise<Response>;
21
+ setup(path: string, body?: Buffer | string | Record<string, unknown>, headers?: Record<string, string>, timeout?: number): Promise<Response>;
30
22
  setParameter(parameter: string, value: string): Promise<Response>;
31
- teardown(path: string, headers?: HeadersInit, timeout?: number): Promise<Response>;
23
+ teardown(path: string, headers?: Record<string, string>, timeout?: number): Promise<Response>;
32
24
  }
33
25
  //#endregion
34
26
  //#region src/pairing.d.ts
@@ -10332,6 +10324,18 @@ declare namespace index_d_exports {
10332
10324
  export { AVEndpointDescriptor, AVEndpointDescriptorSchema, AVOutputDeviceDescriptor, AVOutputDeviceDescriptorSchema, AVOutputDeviceSourceInfo, AVOutputDeviceSourceInfoSchema, ActionType, ActionTypeSchema, ActionType_Enum, ActionType_EnumSchema, ActiveFormatJustification, ActiveFormatJustificationSchema, ActiveFormatJustification_Enum, ActiveFormatJustification_EnumSchema, AlbumTraits, AlbumTraitsSchema, AlbumTraits_Enum, AlbumTraits_EnumSchema, AnimatedArtwork, AnimatedArtworkSchema, AudioBuffer, AudioBufferSchema, AudioDataBlock, AudioDataBlockSchema, AudioFadeMessage, AudioFadeMessageSchema, AudioFadeResponseMessage, AudioFadeResponseMessageSchema, AudioFormat, AudioFormatSchema, AudioFormatSettings, AudioFormatSettingsSchema, AudioRoute, AudioRouteSchema, AudioRouteType, AudioRouteTypeSchema, AudioRouteType_Enum, AudioRouteType_EnumSchema, AudioStreamPacketDescription, AudioStreamPacketDescriptionSchema, AudioTier, AudioTierSchema, AudioTier_Enum, AudioTier_EnumSchema, AudioTime, AudioTimeSchema, AutocapitalizationType, AutocapitalizationTypeSchema, AutocapitalizationType_Enum, AutocapitalizationType_EnumSchema, ChangeType, ChangeTypeSchema, ChangeType_Enum, ChangeType_EnumSchema, ClientUpdatesConfigMessage, ClientUpdatesConfigMessageSchema, ClusterType, ClusterTypeSchema, ClusterType_Enum, ClusterType_EnumSchema, Command, CommandInfo, CommandInfoSchema, CommandOptions, CommandOptionsSchema, CommandSchema, ConfigureConnectionMessage, ConfigureConnectionMessageSchema, ContentItem, ContentItemMetadata, ContentItemMetadataSchema, ContentItemMetadata_MediaSubType, ContentItemMetadata_MediaSubTypeSchema, ContentItemMetadata_MediaType, ContentItemMetadata_MediaTypeSchema, ContentItemSchema, CryptoPairingMessage, CryptoPairingMessageSchema, DataArtwork, DataArtworkSchema, DeviceClass, DeviceClassSchema, DeviceClass_Enum, DeviceClass_EnumSchema, DeviceInfoMessage, DeviceInfoMessageSchema, DeviceSubType, DeviceSubTypeSchema, DeviceSubType_Enum, DeviceSubType_EnumSchema, DeviceType, DeviceTypeSchema, DeviceType_Enum, DeviceType_EnumSchema, Dictionary, DictionarySchema, DisableReason, DisableReasonSchema, DisableReason_Enum, DisableReason_EnumSchema, EndpointOptions, EndpointOptionsSchema, EndpointOptions_Enum, EndpointOptions_EnumSchema, ErrorCode, ErrorCodeSchema, ErrorCode_Enum, ErrorCode_EnumSchema, FormatTier, FormatTierSchema, FormatTier_Enum, FormatTier_EnumSchema, GenericMessage, GenericMessageSchema, GetKeyboardSessionMessage, GetKeyboardSessionMessageSchema, GetRemoteTextInputSessionMessage, GetRemoteTextInputSessionMessageSchema, GetStateMessage, GetStateMessageSchema, GetVolumeMessage, GetVolumeMessageSchema, GetVolumeMutedMessage, GetVolumeMutedMessageSchema, GetVolumeMutedResultMessage, GetVolumeMutedResultMessageSchema, GetVolumeResultMessage, GetVolumeResultMessageSchema, GroupSessionRouteType, GroupSessionRouteTypeSchema, GroupSessionRouteType_Enum, GroupSessionRouteType_EnumSchema, HandlerReturnStatus, HandlerReturnStatusSchema, HandlerReturnStatus_Enum, HandlerReturnStatus_EnumSchema, KeyValuePair, KeyValuePairSchema, KeyboardMessage, KeyboardMessageSchema, KeyboardState, KeyboardStateSchema, KeyboardState_Enum, KeyboardState_EnumSchema, KeyboardType, KeyboardTypeSchema, KeyboardType_Enum, KeyboardType_EnumSchema, LanguageOption, LanguageOptionGroup, LanguageOptionGroupSchema, LanguageOptionSchema, LyricsItem, LyricsItemSchema, LyricsToken, LyricsTokenSchema, ModifyOutputContextRequestMessage, ModifyOutputContextRequestMessageSchema, ModifyOutputContextRequestType, ModifyOutputContextRequestTypeSchema, ModifyOutputContextRequestType_Enum, ModifyOutputContextRequestType_EnumSchema, NotificationMessage, NotificationMessageSchema, NowPlayingClient, NowPlayingClientSchema, NowPlayingInfo, NowPlayingInfoSchema, NowPlayingPlayer, NowPlayingPlayerSchema, Origin, OriginClientPropertiesMessage, OriginClientPropertiesMessageSchema, OriginSchema, Origin_Type, Origin_TypeSchema, PlaybackQueue, PlaybackQueueCapabilities, PlaybackQueueCapabilitiesSchema, PlaybackQueueContext, PlaybackQueueContextSchema, PlaybackQueueRequestMessage, PlaybackQueueRequestMessageSchema, PlaybackQueueSchema, PlaybackQueueType, PlaybackQueueTypeSchema, PlaybackQueueType_Enum, PlaybackQueueType_EnumSchema, PlaybackSessionMigrateRequestEventRole, PlaybackSessionMigrateRequestEventRoleSchema, PlaybackSessionMigrateRequestEventRole_Enum, PlaybackSessionMigrateRequestEventRole_EnumSchema, PlaybackState, PlaybackStateSchema, PlaybackState_Enum, PlaybackState_EnumSchema, PlayerClientPropertiesMessage, PlayerClientPropertiesMessageSchema, PlayerOptions, PlayerOptionsSchema, PlayerOptions_Enum, PlayerOptions_EnumSchema, PlayerPath, PlayerPathSchema, PlaylistTraits, PlaylistTraitsSchema, PlaylistTraits_Enum, PlaylistTraits_EnumSchema, PreferredEncoding, PreferredEncodingSchema, PreferredEncoding_Enum, PreferredEncoding_EnumSchema, PreloadedPlaybackSessionInfo, PreloadedPlaybackSessionInfoSchema, ProtocolMessage, ProtocolMessageSchema, ProtocolMessage_Type, ProtocolMessage_TypeSchema, QueueEndAction, QueueEndActionSchema, QueueEndAction_Enum, QueueEndAction_EnumSchema, RecipeType, RecipeTypeSchema, RecipeType_Enum, RecipeType_EnumSchema, RegisterForGameControllerEventsMessage, RegisterForGameControllerEventsMessageSchema, RegisterForGameControllerEventsMessage_InputModeFlags, RegisterForGameControllerEventsMessage_InputModeFlagsSchema, RegisterHIDDeviceMessage, RegisterHIDDeviceMessageSchema, RegisterHIDDeviceResultMessage, RegisterHIDDeviceResultMessageSchema, RegisterVoiceInputDeviceMessage, RegisterVoiceInputDeviceMessageSchema, RegisterVoiceInputDeviceResponseMessage, RegisterVoiceInputDeviceResponseMessageSchema, RemoteArtwork, RemoteArtworkSchema, RemoteTextInputMessage, RemoteTextInputMessageSchema, RemoveClientMessage, RemoveClientMessageSchema, RemoveEndpointsMessage, RemoveEndpointsMessageSchema, RemoveOutputDevicesMessage, RemoveOutputDevicesMessageSchema, RemovePlayerMessage, RemovePlayerMessageSchema, RepeatMode, RepeatModeSchema, RepeatMode_Enum, RepeatMode_EnumSchema, ReplaceIntent, ReplaceIntentSchema, ReplaceIntent_Enum, ReplaceIntent_EnumSchema, ReturnKeyType, ReturnKeyTypeSchema, ReturnKeyType_Enum, ReturnKeyType_EnumSchema, SendButtonEventMessage, SendButtonEventMessageSchema, SendCommandMessage, SendCommandMessageSchema, SendCommandResult, SendCommandResultMessage, SendCommandResultMessageSchema, SendCommandResultSchema, SendCommandResultStatus, SendCommandResultStatusSchema, SendCommandResultType, SendCommandResultTypeSchema, SendCommandResultType_Enum, SendCommandResultType_EnumSchema, SendCommandStatusCode, SendCommandStatusCodeSchema, SendCommandStatusCode_Enum, SendCommandStatusCode_EnumSchema, SendError, SendErrorSchema, SendError_Enum, SendError_EnumSchema, SendHIDEventMessage, SendHIDEventMessageSchema, SendHIDReportMessage, SendHIDReportMessageSchema, SendPackedVirtualTouchEventMessage, SendPackedVirtualTouchEventMessageSchema, SendPackedVirtualTouchEventMessage_Phase, SendPackedVirtualTouchEventMessage_PhaseSchema, SendVirtualTouchEventMessage, SendVirtualTouchEventMessageSchema, SendVoiceInputMessage, SendVoiceInputMessageSchema, SetArtworkMessage, SetArtworkMessageSchema, SetConnectionStateMessage, SetConnectionStateMessageSchema, SetConnectionStateMessage_ConnectionState, SetConnectionStateMessage_ConnectionStateSchema, SetDefaultSupportedCommandsMessage, SetDefaultSupportedCommandsMessageSchema, SetDiscoveryModeMessage, SetDiscoveryModeMessageSchema, SetHiliteModeMessage, SetHiliteModeMessageSchema, SetNowPlayingClientMessage, SetNowPlayingClientMessageSchema, SetNowPlayingPlayerMessage, SetNowPlayingPlayerMessageSchema, SetReadyStateMessage, SetReadyStateMessageSchema, SetRecordingStateMessage, SetRecordingStateMessageSchema, SetRecordingStateMessage_RecordingState, SetRecordingStateMessage_RecordingStateSchema, SetStateMessage, SetStateMessageSchema, SetVolumeMessage, SetVolumeMessageSchema, SetVolumeMutedMessage, SetVolumeMutedMessageSchema, ShuffleMode, ShuffleModeSchema, ShuffleMode_Enum, ShuffleMode_EnumSchema, SongTraits, SongTraitsSchema, SongTraits_Enum, SongTraits_EnumSchema, SupportedCommands, SupportedCommandsSchema, SystemPlaybackCustomData, SystemPlaybackCustomDataSchema, SystemPlaybackGenericTracklistQueue, SystemPlaybackGenericTracklistQueueSchema, SystemPlaybackQueue, SystemPlaybackQueueSchema, TextEditingAttributes, TextEditingAttributesSchema, TextInputMessage, TextInputMessageSchema, TextInputTraits, TextInputTraitsSchema, TransactionKey, TransactionKeySchema, TransactionMessage, TransactionMessageSchema, TransactionPacket, TransactionPacketSchema, TransactionPackets, TransactionPacketsSchema, TranscriptAlignment, TranscriptAlignmentSchema, UpdateClientMessage, UpdateClientMessageSchema, UpdateContentItemArtworkMessage, UpdateContentItemArtworkMessageSchema, UpdateContentItemMessage, UpdateContentItemMessageSchema, UpdateEndPointsMessage, UpdateEndPointsMessageSchema, UpdateOutputDeviceMessage, UpdateOutputDeviceMessageSchema, UpdatePlayerMessage, UpdatePlayerMessageSchema, Value, ValueSchema, VirtualTouchDeviceDescriptor, VirtualTouchDeviceDescriptorSchema, VirtualTouchEvent, VirtualTouchEventSchema, VirtualTouchPhase, VirtualTouchPhaseSchema, VirtualTouchPhase_Enum, VirtualTouchPhase_EnumSchema, VoiceInputDeviceDescriptor, VoiceInputDeviceDescriptorSchema, VolumeCapabilities, VolumeCapabilitiesSchema, VolumeCapabilities_Enum, VolumeCapabilities_EnumSchema, VolumeControlAvailabilityMessage, VolumeControlAvailabilityMessageSchema, VolumeControlCapabilitiesDidChangeMessage, VolumeControlCapabilitiesDidChangeMessageSchema, VolumeDidChangeMessage, VolumeDidChangeMessageSchema, VolumeMutedDidChangeMessage, VolumeMutedDidChangeMessageSchema, WakeDeviceMessage, WakeDeviceMessageSchema, audioFadeMessage, audioFadeResponseMessage, clientUpdatesConfigMessage, configureConnectionMessage, cryptoPairingMessage, deviceInfoMessage, file_Artwork, file_AudioFadeMessage, file_AudioFadeResponseMessage, file_AudioFormatSettingsMessage, file_ClientUpdatesConfigMessage, file_CommandInfo, file_CommandOptions, file_Common, file_ConfigureConnectionMessage, file_ContentItem, file_ContentItemMetadata, file_CryptoPairingMessage, file_DeviceInfoMessage, file_Dictionary, file_GenericMessage, file_GetKeyboardSessionMessage, file_GetRemoteTextInputSessionMessage, file_GetStateMessage, file_GetVolumeMessage, file_GetVolumeMutedMessage, file_GetVolumeMutedResultMessage, file_GetVolumeResultMessage, file_KeyboardMessage, file_LanguageOption, file_LyricsItem, file_ModifyOutputContextRequestMessage, file_NotificationMessage, file_NowPlayingClient, file_NowPlayingInfo, file_NowPlayingPlayer, file_Origin, file_OriginClientPropertiesMessage, file_PlaybackQueue, file_PlaybackQueueCapabilities, file_PlaybackQueueContext, file_PlaybackQueueRequestMessage, file_PlayerClientPropertiesMessage, file_PlayerPath, file_ProtocolMessage, file_RegisterForGameControllerEventsMessage, file_RegisterHIDDeviceMessage, file_RegisterHIDDeviceResultMessage, file_RegisterVoiceInputDeviceMessage, file_RegisterVoiceInputDeviceResponseMessage, file_RemoteTextInputMessage, file_RemoveClientMessage, file_RemoveEndpointsMessage, file_RemoveOutputDevicesMessage, file_RemovePlayerMessage, file_SendButtonEventMessage, file_SendCommandMessage, file_SendCommandResultMessage, file_SendHIDEventMessage, file_SendHIDReportMessage, file_SendPackedVirtualTouchEventMessage, file_SendVirtualTouchEventMessage, file_SendVoiceInputMessage, file_SetArtworkMessage, file_SetConnectionStateMessage, file_SetDefaultSupportedCommandsMessage, file_SetDiscoveryModeMessage, file_SetHiliteModeMessage, file_SetNowPlayingClientMessage, file_SetNowPlayingPlayerMessage, file_SetReadyStateMessage, file_SetRecordingStateMessage, file_SetStateMessage, file_SetVolumeMessage, file_SetVolumeMutedMessage, file_SupportedCommands, file_SystemPlaybackQueue, file_TextInputMessage, file_TransactionKey, file_TransactionMessage, file_TransactionPacket, file_TransactionPackets, file_Transcript, file_UpdateClientMessage, file_UpdateContentItemArtworkMessage, file_UpdateContentItemMessage, file_UpdateEndPointsMessage, file_UpdateOutputDeviceMessage, file_UpdatePlayerPath, file_VirtualTouchDeviceDescriptorMessage, file_VoiceInputDeviceDescriptorMessage, file_VolumeControlAvailabilityMessage, file_VolumeControlCapabilitiesDidChangeMessage, file_VolumeDidChangeMessage, file_VolumeMutedDidChangeMessage, file_WakeDeviceMessage, genericMessage, getKeyboardSessionMessage, getRemoteTextInputSessionMessage, getStateMessage, getVolumeMessage, getVolumeMutedMessage, getVolumeMutedResultMessage, getVolumeResultMessage, keyboardMessage, modifyOutputContextRequestMessage, notificationMessage, originClientPropertiesMessage, playbackQueueRequestMessage, playerClientPropertiesMessage, readyStateMessage, registerForGameControllerEventsMessage, registerHIDDeviceMessage, registerHIDDeviceResultMessage, registerVoiceInputDeviceMessage, registerVoiceInputDeviceResponseMessage, remoteTextInputMessage, removeClientMessage, removeEndpointsMessage, removeOutputDevicesMessage, removePlayerMessage, sendButtonEventMessage, sendCommandMessage, sendCommandResultMessage, sendHIDEventMessage, sendHIDReportMessage, sendPackedVirtualTouchEventMessage, sendVirtualTouchEventMessage, sendVoiceInputMessage, setArtworkMessage, setConnectionStateMessage, setDefaultSupportedCommandsMessage, setDiscoveryModeMessage, setHiliteModeMessage, setNowPlayingClientMessage, setNowPlayingPlayerMessage, setRecordingStateMessage, setStateMessage, setVolumeMessage, setVolumeMutedMessage, textInputMessage, transactionMessage, updateClientMessage, updateContentItemArtworkMessage, updateContentItemMessage, updateEndPointsMessage, updateOutputDeviceMessage, updatePlayerMessage, volumeControlAvailabilityMessage, volumeControlCapabilitiesDidChangeMessage, volumeDidChangeMessage, volumeMutedDidChangeMessage, wakeDeviceMessage };
10333
10325
  }
10334
10326
  //#endregion
10327
+ //#region src/baseStream.d.ts
10328
+ type DefaultEventMap = {
10329
+ close: [];
10330
+ connect: [];
10331
+ error: [Error];
10332
+ timeout: [];
10333
+ };
10334
+ declare class BaseStream<TEventMap extends EventMap = {}> extends EncryptionAwareConnection<DefaultEventMap & TEventMap> {
10335
+ decrypt(data: Buffer): Buffer | false;
10336
+ encrypt(data: Buffer): Buffer;
10337
+ }
10338
+ //#endregion
10335
10339
  //#region src/dataStream.d.ts
10336
10340
  type EventMap$1 = {
10337
10341
  readonly rawMessage: [ProtocolMessage];
@@ -10371,7 +10375,7 @@ declare class EventStream extends BaseStream {
10371
10375
  #private;
10372
10376
  constructor(context: Context, address: string, port: number);
10373
10377
  disconnect(): Promise<void>;
10374
- respond(response: Response): Promise<void>;
10378
+ respond(status: number, statusText: string, headers?: Record<string, string | number>, body?: Buffer): void;
10375
10379
  setup(sharedSecret: Buffer): void;
10376
10380
  }
10377
10381
  //#endregion
@@ -10441,4 +10445,4 @@ declare function setVolumeMuted(outputDeviceUID: string, isMuted: boolean): [Pro
10441
10445
  declare function wakeDevice(): [ProtocolMessage, DescExtension];
10442
10446
  declare function getExtension<Desc extends DescExtension>(message: Extendee<Desc>, extension: Desc): ExtensionValueShape<Desc>;
10443
10447
  //#endregion
10444
- export { AudioStream, BaseStream, ControlStream, DataStream, dataStreamMessages_d_exports as DataStreamMessage, EventStream, Pairing, index_d_exports as Proto, Protocol, Verify };
10448
+ export { AudioStream, ControlStream, DataStream, dataStreamMessages_d_exports as DataStreamMessage, EventStream, Pairing, index_d_exports as Proto, Protocol, Verify };
package/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { createSocket } from "node:dgram";
4
- import { AccessoryPair, AccessoryVerify, Context, EncryptionAwareConnection, HTTP_TIMEOUT, generateActiveRemoteId, generateDacpId, generateSessionId, getMacAddress, randomInt32, randomInt64, uint16ToBE, uuid } from "@basmilius/apple-common";
5
- import { NTP, Plist, RTSP } from "@basmilius/apple-encoding";
4
+ import { AccessoryPair, AccessoryVerify, Context, EncryptionAwareConnection, EncryptionState, HTTP_TIMEOUT, generateActiveRemoteId, generateDacpId, generateSessionId, getMacAddress, randomInt32, randomInt64, uint16ToBE, uuid } from "@basmilius/apple-common";
5
+ import { NTP, Plist } from "@basmilius/apple-encoding";
6
6
  import { Chacha20, hkdf } from "@basmilius/apple-encryption";
7
+ import { RtspClient, buildResponse, parseRequest } from "@basmilius/apple-rtsp";
7
8
 
8
9
  //#region src/audioStream.ts
9
10
  const SAMPLE_RATE = 44100;
@@ -10681,54 +10682,43 @@ function readVariant(buf, offset = 0) {
10681
10682
  }
10682
10683
 
10683
10684
  //#endregion
10684
- //#region src/baseStream.ts
10685
- var BaseStream = class extends EncryptionAwareConnection {
10686
- get #encryptionState() {
10687
- return this._encryption;
10688
- }
10689
- decrypt(data) {
10690
- const result = [];
10691
- let offset = 0;
10692
- let readCount = this.#encryptionState.readCount ?? 0;
10693
- while (offset < data.length) {
10694
- if (offset + 2 > data.length) {
10695
- this.context.logger.warn("Expected frame length to be within buffer bounds.");
10696
- return false;
10697
- }
10698
- const frameLength = data.readUInt16LE(offset);
10699
- offset += 2;
10700
- const end = offset + frameLength + 16;
10701
- if (end > data.length) {
10702
- this.context.logger.warn(`Truncated frame end=${end} length=${data.length}`);
10703
- return false;
10704
- }
10705
- const ciphertext = data.subarray(offset, offset + frameLength);
10706
- const authTag = data.subarray(offset + frameLength, end);
10707
- offset = end;
10708
- const plaintext = Chacha20.decrypt(this.#encryptionState.readKey, nonce(readCount++), Buffer.from(Uint16Array.of(frameLength).buffer.slice(0, 2)), ciphertext, authTag);
10709
- result.push(plaintext);
10710
- }
10711
- this.#encryptionState.readCount = readCount;
10712
- return Buffer.concat(result);
10713
- }
10714
- encrypt(data) {
10715
- const FRAME_LENGTH = 1024;
10716
- const result = [];
10717
- for (let offset = 0; offset < data.length;) {
10718
- const frame = data.subarray(offset, offset + FRAME_LENGTH);
10719
- offset += frame.length;
10720
- const leLength = Buffer.allocUnsafe(2);
10721
- leLength.writeUInt16LE(frame.length, 0);
10722
- const encrypted = Chacha20.encrypt(this.#encryptionState.writeKey, nonce(this.#encryptionState.writeCount++), leLength, frame);
10723
- result.push(leLength, encrypted.ciphertext, encrypted.authTag);
10724
- }
10725
- return Buffer.concat(result);
10726
- }
10727
- };
10685
+ //#region src/encryption.ts
10686
+ function chacha20Decrypt(state, data) {
10687
+ const result = [];
10688
+ let offset = 0;
10689
+ let readCount = state.readCount ?? 0;
10690
+ while (offset < data.length) {
10691
+ if (offset + 2 > data.length) return false;
10692
+ const frameLength = data.readUInt16LE(offset);
10693
+ offset += 2;
10694
+ const end = offset + frameLength + 16;
10695
+ if (end > data.length) return false;
10696
+ const ciphertext = data.subarray(offset, offset + frameLength);
10697
+ const authTag = data.subarray(offset + frameLength, end);
10698
+ offset = end;
10699
+ const plaintext = Chacha20.decrypt(state.readKey, nonce(readCount++), Buffer.from(Uint16Array.of(frameLength).buffer.slice(0, 2)), ciphertext, authTag);
10700
+ result.push(plaintext);
10701
+ }
10702
+ state.readCount = readCount;
10703
+ return Buffer.concat(result);
10704
+ }
10705
+ function chacha20Encrypt(state, data) {
10706
+ const FRAME_LENGTH = 1024;
10707
+ const result = [];
10708
+ for (let offset = 0; offset < data.length;) {
10709
+ const frame = data.subarray(offset, offset + FRAME_LENGTH);
10710
+ offset += frame.length;
10711
+ const leLength = Buffer.allocUnsafe(2);
10712
+ leLength.writeUInt16LE(frame.length, 0);
10713
+ const encrypted = Chacha20.encrypt(state.writeKey, nonce(state.writeCount++), leLength, frame);
10714
+ result.push(leLength, encrypted.ciphertext, encrypted.authTag);
10715
+ }
10716
+ return Buffer.concat(result);
10717
+ }
10728
10718
 
10729
10719
  //#endregion
10730
10720
  //#region src/controlStream.ts
10731
- var ControlStream = class extends BaseStream {
10721
+ var ControlStream = class extends RtspClient {
10732
10722
  get activeRemoteId() {
10733
10723
  return this.#activeRemoteId;
10734
10724
  }
@@ -10738,126 +10728,109 @@ var ControlStream = class extends BaseStream {
10738
10728
  get sessionId() {
10739
10729
  return this.#sessionId;
10740
10730
  }
10731
+ get isEncrypted() {
10732
+ return !!this.#encryptionState;
10733
+ }
10741
10734
  #activeRemoteId;
10742
10735
  #dacpId;
10743
10736
  #sessionId;
10744
- #buffer = Buffer.alloc(0);
10745
- #cseq = 0;
10746
- #requesting = false;
10747
- #requestTimer;
10748
- #reject;
10749
- #resolve;
10737
+ #encryptionState;
10750
10738
  constructor(context, address, port) {
10751
10739
  super(context, address, port);
10752
10740
  this.#activeRemoteId = generateActiveRemoteId();
10753
10741
  this.#dacpId = generateDacpId();
10754
10742
  this.#sessionId = generateSessionId();
10755
- this.on("close", this.#onClose.bind(this));
10756
- this.on("data", this.#onData.bind(this));
10757
- this.on("error", this.#onError.bind(this));
10758
- this.on("timeout", this.#onTimeout.bind(this));
10759
10743
  }
10760
- async disconnect() {
10761
- this.#cleanup();
10762
- await super.disconnect();
10744
+ enableEncryption(readKey, writeKey) {
10745
+ this.#encryptionState = new EncryptionState(readKey, writeKey);
10746
+ }
10747
+ getDefaultHeaders() {
10748
+ return {
10749
+ "Active-Remote": this.#activeRemoteId,
10750
+ "Client-Instance": this.#dacpId,
10751
+ "DACP-ID": this.#dacpId,
10752
+ "User-Agent": "AirPlay/320.20",
10753
+ "X-Apple-ProtocolVersion": 1,
10754
+ "X-Apple-Session-ID": this.#sessionId,
10755
+ "X-ProtocolVersion": 1
10756
+ };
10757
+ }
10758
+ transformIncoming(data) {
10759
+ if (!this.#encryptionState) return data;
10760
+ return chacha20Decrypt(this.#encryptionState, data);
10761
+ }
10762
+ transformOutgoing(data) {
10763
+ if (!this.#encryptionState) return data;
10764
+ return chacha20Encrypt(this.#encryptionState, data);
10763
10765
  }
10764
10766
  async flush(uri, headers) {
10765
- return await this.#request("FLUSH", uri, null, headers);
10767
+ return await this.exchange("FLUSH", uri, {
10768
+ headers,
10769
+ allowError: true
10770
+ });
10766
10771
  }
10767
10772
  async get(path, headers = {}, timeout = HTTP_TIMEOUT) {
10768
- return await this.#request("GET", path, null, headers, timeout);
10773
+ return await this.exchange("GET", path, {
10774
+ headers,
10775
+ timeout,
10776
+ allowError: true
10777
+ });
10769
10778
  }
10770
- async post(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
10771
- return await this.#request("POST", path, body, headers, timeout);
10779
+ async post(path, body, headers = {}, timeout = HTTP_TIMEOUT) {
10780
+ return await this.exchange("POST", path, {
10781
+ headers,
10782
+ body,
10783
+ timeout,
10784
+ allowError: true
10785
+ });
10772
10786
  }
10773
- async put(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
10774
- return await this.#request("PUT", path, body, headers, timeout);
10787
+ async put(path, body, headers = {}, timeout = HTTP_TIMEOUT) {
10788
+ return await this.exchange("PUT", path, {
10789
+ headers,
10790
+ body,
10791
+ timeout,
10792
+ allowError: true
10793
+ });
10775
10794
  }
10776
10795
  async record(path, headers = {}, timeout = HTTP_TIMEOUT) {
10777
- return await this.#request("RECORD", path, null, headers, timeout);
10796
+ return await this.exchange("RECORD", path, {
10797
+ headers,
10798
+ timeout,
10799
+ allowError: true
10800
+ });
10778
10801
  }
10779
- async setup(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
10780
- return await this.#request("SETUP", path, body, headers, timeout);
10802
+ async setup(path, body, headers = {}, timeout = HTTP_TIMEOUT) {
10803
+ return await this.exchange("SETUP", path, {
10804
+ headers,
10805
+ body,
10806
+ timeout,
10807
+ allowError: true
10808
+ });
10781
10809
  }
10782
10810
  async setParameter(parameter, value) {
10783
- return await this.#request("SET_PARAMETER", `/${this.sessionId}`, `${parameter}: ${value}\r\n`, { "Content-Type": "text/parameters" });
10811
+ return await this.exchange("SET_PARAMETER", `/${this.sessionId}`, {
10812
+ contentType: "text/parameters",
10813
+ body: `${parameter}: ${value}\r\n`,
10814
+ allowError: true
10815
+ });
10784
10816
  }
10785
10817
  async teardown(path, headers = {}, timeout = HTTP_TIMEOUT) {
10786
- return await this.#request("TEARDOWN", path, null, headers, timeout);
10787
- }
10788
- #cleanup() {
10789
- if (this.#requestTimer) {
10790
- clearTimeout(this.#requestTimer);
10791
- this.#requestTimer = void 0;
10792
- }
10793
- this.#buffer = Buffer.alloc(0);
10794
- this.#reject = void 0;
10795
- this.#resolve = void 0;
10796
- this.#requesting = false;
10797
- }
10798
- #handle(data, err) {
10799
- if (this.#requestTimer) {
10800
- clearTimeout(this.#requestTimer);
10801
- this.#requestTimer = void 0;
10802
- }
10803
- if (err) this.#reject?.(err);
10804
- else this.#resolve?.(data);
10805
- this.#reject = void 0;
10806
- this.#resolve = void 0;
10807
- this.#requesting = false;
10808
- }
10809
- #request(method, path, body, headers, timeout = HTTP_TIMEOUT) {
10810
- if (this.#requesting) return Promise.reject(/* @__PURE__ */ new Error("Another request is currently being made."));
10811
- this.#requesting = true;
10812
- const cseq = this.#cseq++;
10813
- let data;
10814
- if (body) {
10815
- headers["Content-Length"] = Buffer.byteLength(body);
10816
- data = Buffer.concat([Buffer.from(RTSP.makeHeader(method, path, headers, cseq, this.#activeRemoteId, this.#dacpId, this.#sessionId)), Buffer.from(body)]);
10817
- } else {
10818
- headers["Content-Length"] = 0;
10819
- data = Buffer.from(RTSP.makeHeader(method, path, headers, cseq, this.#activeRemoteId, this.#dacpId, this.#sessionId));
10820
- }
10821
- this.context.logger.net("[control]", method, path, `cseq = ${cseq}`);
10822
- if (this.isEncrypted) data = this.encrypt(data);
10823
- return new Promise((resolve, reject) => {
10824
- this.#reject = reject;
10825
- this.#resolve = resolve;
10826
- this.#requestTimer = setTimeout(() => this.#handle(void 0, /* @__PURE__ */ new Error("Request timed out")), timeout);
10827
- this.write(data);
10818
+ return await this.exchange("TEARDOWN", path, {
10819
+ headers,
10820
+ timeout,
10821
+ allowError: true
10828
10822
  });
10829
10823
  }
10830
- #onClose() {
10831
- this.#cleanup();
10832
- this.#handle(void 0, /* @__PURE__ */ new Error("Connection closed."));
10833
- this.context.logger.net("[control]", "#onClose()");
10834
- }
10835
- #onData(data) {
10836
- try {
10837
- this.#buffer = Buffer.concat([this.#buffer, data]);
10838
- if (this.isEncrypted) {
10839
- const decrypted = this.decrypt(this.#buffer);
10840
- if (!decrypted) return;
10841
- this.#buffer = decrypted;
10842
- }
10843
- while (this.#buffer.byteLength > 0) {
10844
- const result = RTSP.makeResponse(this.#buffer);
10845
- if (result === null) return;
10846
- this.#buffer = this.#buffer.subarray(result.responseLength);
10847
- this.#handle(result.response, void 0);
10848
- }
10849
- } catch (err) {
10850
- this.context.logger.error("[control]", "#onData()", err);
10851
- this.emit("error", err);
10852
- }
10853
- }
10854
- #onError(err) {
10855
- this.#handle(void 0, err);
10856
- this.context.logger.error("[control]", "#onError()", err);
10824
+ };
10825
+
10826
+ //#endregion
10827
+ //#region src/baseStream.ts
10828
+ var BaseStream = class extends EncryptionAwareConnection {
10829
+ decrypt(data) {
10830
+ return chacha20Decrypt(this._encryption, data);
10857
10831
  }
10858
- #onTimeout() {
10859
- this.#handle(void 0, /* @__PURE__ */ new Error("Request timed out."));
10860
- this.context.logger.net("[control]", "#onTimeout()");
10832
+ encrypt(data) {
10833
+ return chacha20Encrypt(this._encryption, data);
10861
10834
  }
10862
10835
  };
10863
10836
 
@@ -11133,19 +11106,13 @@ var EventStream = class extends BaseStream {
11133
11106
  this.#cleanup();
11134
11107
  await super.disconnect();
11135
11108
  }
11136
- async respond(response) {
11137
- const body = Buffer.from(await response.arrayBuffer());
11138
- const header = [];
11139
- header.push(`RTSP/1.0 ${response.status} ${response.statusText}`);
11140
- for (const [name, value] of Object.entries(response.headers)) header.push(`${name}: ${value}`);
11141
- if (body.byteLength > 0) header.push(`Content-Length: ${body.byteLength}`);
11142
- else header.push("Content-Length: 0");
11143
- header.push("");
11144
- header.push("");
11145
- const headers = header.join("\r\n");
11146
- let data;
11147
- if (response.body) data = Buffer.concat([Buffer.from(headers), body]);
11148
- else data = Buffer.from(headers);
11109
+ respond(status, statusText, headers, body) {
11110
+ let data = buildResponse({
11111
+ status,
11112
+ statusText,
11113
+ headers,
11114
+ body
11115
+ });
11149
11116
  if (this.isEncrypted) data = this.encrypt(data);
11150
11117
  this.write(data);
11151
11118
  }
@@ -11175,15 +11142,10 @@ var EventStream = class extends BaseStream {
11175
11142
  case "POST /command":
11176
11143
  const data = Plist.parse(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
11177
11144
  this.context.logger.info("[event]", "Received event stream request.", data);
11178
- const response = new Response(null, {
11179
- status: 200,
11180
- statusText: "OK",
11181
- headers: {
11182
- "Audio-Latency": "0",
11183
- "CSeq": (headers["CSeq"] ?? 0).toString()
11184
- }
11145
+ this.respond(200, "OK", {
11146
+ "Audio-Latency": 0,
11147
+ "CSeq": headers["CSeq"] ?? 0
11185
11148
  });
11186
- await this.respond(response);
11187
11149
  break;
11188
11150
  default:
11189
11151
  this.context.logger.warn("[event]", "No handler for url", key);
@@ -11205,7 +11167,7 @@ var EventStream = class extends BaseStream {
11205
11167
  this.#buffer = decrypted;
11206
11168
  }
11207
11169
  while (this.#buffer.byteLength > 0) {
11208
- const result = RTSP.makeRequest(this.#buffer);
11170
+ const result = parseRequest(this.#buffer);
11209
11171
  if (result === null) return;
11210
11172
  this.#buffer = this.#buffer.subarray(result.requestLength);
11211
11173
  await this.#handle(result.method, result.path, result.headers, result.body);
@@ -11368,16 +11330,24 @@ var Protocol = class {
11368
11330
  disconnect() {
11369
11331
  try {
11370
11332
  this.#audioStream?.close();
11371
- } catch {}
11333
+ } catch (err) {
11334
+ this.#context.logger.warn("[protocol]", "Error closing audio stream", err);
11335
+ }
11372
11336
  try {
11373
11337
  this.#dataStream?.destroy();
11374
- } catch {}
11338
+ } catch (err) {
11339
+ this.#context.logger.warn("[protocol]", "Error destroying data stream", err);
11340
+ }
11375
11341
  try {
11376
11342
  this.#eventStream?.destroy();
11377
- } catch {}
11343
+ } catch (err) {
11344
+ this.#context.logger.warn("[protocol]", "Error destroying event stream", err);
11345
+ }
11378
11346
  try {
11379
11347
  this.#controlStream.destroy();
11380
- } catch {}
11348
+ } catch (err) {
11349
+ this.#context.logger.warn("[protocol]", "Error destroying control stream", err);
11350
+ }
11381
11351
  this.#audioStream = void 0;
11382
11352
  this.#dataStream = void 0;
11383
11353
  this.#eventStream = void 0;
@@ -11388,7 +11358,7 @@ var Protocol = class {
11388
11358
  }
11389
11359
  async setupDataStream(sharedSecret, onBeforeConnect) {
11390
11360
  const seed = randomInt64();
11391
- const request = Plist.serialize({ streams: [{
11361
+ const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, { streams: [{
11392
11362
  controlType: 2,
11393
11363
  channelID: uuid().toUpperCase(),
11394
11364
  seed,
@@ -11397,7 +11367,6 @@ var Protocol = class {
11397
11367
  wantsDedicatedSocket: true,
11398
11368
  clientTypeUUID: "1910A70F-DBC0-4242-AF95-115DB30604E1"
11399
11369
  }] });
11400
- const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, Buffer.from(request), { "Content-Type": "application/x-apple-binary-plist" });
11401
11370
  if (response.status !== 200) {
11402
11371
  this.context.logger.error("[protocol]", "Failed to setup data stream.", response.status, response.statusText, await response.text());
11403
11372
  throw new Error("Failed to setup data stream.");
@@ -11427,8 +11396,7 @@ var Protocol = class {
11427
11396
  body.timingPort = this.#timingServer.port;
11428
11397
  body.timingProtocol = "NTP";
11429
11398
  }
11430
- const request = Plist.serialize(body);
11431
- const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, Buffer.from(request), { "Content-Type": "application/x-apple-binary-plist" });
11399
+ const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, body);
11432
11400
  if (response.status !== 200) {
11433
11401
  this.context.logger.error("[protocol]", "Failed to setup event stream.", response.status, response.statusText, await response.text());
11434
11402
  throw new Error("Failed to setup event stream.");
@@ -11466,8 +11434,7 @@ var Protocol = class {
11466
11434
  body.timingPort = this.#timingServer.port;
11467
11435
  body.timingProtocol = "NTP";
11468
11436
  }
11469
- const request = Plist.serialize(body);
11470
- const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, Buffer.from(request), { "Content-Type": "application/x-apple-binary-plist" });
11437
+ const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, body);
11471
11438
  if (response.status !== 200) {
11472
11439
  this.context.logger.error("[protocol]", "Failed to setup event stream.", response.status, response.statusText, await response.text());
11473
11440
  throw new Error("Failed to setup event stream.");
@@ -11499,14 +11466,14 @@ var Protocol = class {
11499
11466
  setupBody.timingPort = this.#timingServer.port;
11500
11467
  setupBody.timingProtocol = "NTP";
11501
11468
  } else setupBody.timingProtocol = "None";
11502
- const setupResponse = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, Buffer.from(Plist.serialize(setupBody)), { "Content-Type": "application/x-apple-binary-plist" });
11469
+ const setupResponse = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, setupBody);
11503
11470
  if (setupResponse.status !== 200) throw new Error(`Failed to setup for playback: ${setupResponse.status}`);
11504
11471
  const eventPort = Plist.parse(await setupResponse.arrayBuffer()).eventPort & 65535;
11505
11472
  this.#eventStream = new EventStream(this.#context, this.#controlStream.address, eventPort);
11506
11473
  this.#eventStream.setup(sharedSecret);
11507
11474
  await this.#eventStream.connect();
11508
11475
  await this.#controlStream.record(`/${this.#controlStream.sessionId}`);
11509
- const playBody = Plist.serialize({
11476
+ const response = await this.#controlStream.post("/play", {
11510
11477
  "Content-Location": url,
11511
11478
  "Start-Position-Seconds": position,
11512
11479
  uuid: this.#sessionUUID.toUpperCase(),
@@ -11520,7 +11487,6 @@ var Protocol = class {
11520
11487
  model: "iPhone16,2",
11521
11488
  SenderMACAddress: getMacAddress().toUpperCase()
11522
11489
  });
11523
- const response = await this.#controlStream.post("/play", Buffer.from(playBody), { "Content-Type": "application/x-apple-binary-plist" });
11524
11490
  this.context.logger.info("[protocol]", `play_url response: ${response.status}`);
11525
11491
  if (response.status !== 200) throw new Error(`Failed to play URL: ${response.status}`);
11526
11492
  await this.#putProperty("isInterestedInDateRange", { value: true });
@@ -11540,7 +11506,7 @@ var Protocol = class {
11540
11506
  } });
11541
11507
  }
11542
11508
  async #putProperty(property, body) {
11543
- await this.#controlStream.put(`/setProperty?${property}`, Buffer.from(Plist.serialize(body)), { "Content-Type": "application/x-apple-binary-plist" });
11509
+ await this.#controlStream.put(`/setProperty?${property}`, body);
11544
11510
  }
11545
11511
  async setupAudioStream(source) {
11546
11512
  this.#audioStream = new AudioStream(this);
@@ -11792,4 +11758,4 @@ function getExtension(message, extension) {
11792
11758
  }
11793
11759
 
11794
11760
  //#endregion
11795
- export { AudioStream, BaseStream, ControlStream, DataStream, dataStreamMessages_exports as DataStreamMessage, EventStream, Pairing, proto_exports as Proto, Protocol, Verify };
11761
+ export { AudioStream, ControlStream, DataStream, dataStreamMessages_exports as DataStreamMessage, EventStream, Pairing, proto_exports as Proto, Protocol, Verify };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@basmilius/apple-airplay",
3
3
  "description": "Implementation of Apple's AirPlay2 in Node.js.",
4
- "version": "0.9.16",
4
+ "version": "0.9.18",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -48,9 +48,10 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@basmilius/apple-common": "0.9.16",
52
- "@basmilius/apple-encoding": "0.9.16",
53
- "@basmilius/apple-encryption": "0.9.16"
51
+ "@basmilius/apple-common": "0.9.18",
52
+ "@basmilius/apple-encoding": "0.9.18",
53
+ "@basmilius/apple-encryption": "0.9.18",
54
+ "@basmilius/apple-rtsp": "0.9.18"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@bufbuild/buf": "^1.66.1",