@basmilius/apple-common 0.10.1 → 0.12.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
@@ -13,61 +13,22 @@ declare function v4(options?: Version4Options, buf?: undefined, offset?: number)
13
13
  declare function v4<TBuf extends Uint8Array = Uint8Array>(options: Version4Options | undefined, buf: TBuf, offset?: number): TBuf;
14
14
  //#endregion
15
15
  //#region src/airplayFeatures.d.ts
16
- /** Type definition for the AirPlay feature flags bitmask object. */
17
- type AirPlayFeatureFlagsType = {
18
- readonly SupportsAirPlayVideoV1: bigint;
19
- readonly SupportsAirPlayPhoto: bigint;
20
- readonly SupportsAirPlayVideoFairPlay: bigint;
21
- readonly SupportsAirPlayVideoVolumeControl: bigint;
22
- readonly SupportsAirPlayVideoHTTPLiveStreams: bigint;
23
- readonly SupportsAirPlaySlideShow: bigint;
24
- readonly SupportsAirPlayScreen: bigint;
25
- readonly SupportsAirPlayAudio: bigint;
26
- readonly AudioRedundant: bigint;
27
- readonly Authentication_4: bigint;
28
- readonly MetadataFeatures_0: bigint;
29
- readonly MetadataFeatures_1: bigint;
30
- readonly MetadataFeatures_2: bigint;
31
- readonly AudioFormats_0: bigint;
32
- readonly AudioFormats_1: bigint;
33
- readonly AudioFormats_2: bigint;
34
- readonly AudioFormats_3: bigint;
35
- readonly Authentication_1: bigint;
36
- readonly Authentication_8: bigint;
37
- readonly SupportsLegacyPairing: bigint;
38
- readonly HasUnifiedAdvertiserInfo: bigint;
39
- readonly IsCarPlay: bigint;
40
- readonly SupportsAirPlayVideoPlayQueue: bigint;
41
- readonly SupportsAirPlayFromCloud: bigint;
42
- readonly SupportsTLS_PSK: bigint;
43
- readonly SupportsUnifiedMediaControl: bigint;
44
- readonly SupportsBufferedAudio: bigint;
45
- readonly SupportsPTP: bigint;
46
- readonly SupportsScreenMultiCodec: bigint;
47
- readonly SupportsSystemPairing: bigint;
48
- readonly IsAPValeriaScreenSender: bigint;
49
- readonly SupportsHKPairingAndAccessControl: bigint;
50
- readonly SupportsCoreUtilsPairingAndEncryption: bigint;
51
- readonly SupportsAirPlayVideoV2: bigint;
52
- readonly MetadataFeatures_3: bigint;
53
- readonly SupportsUnifiedPairSetupAndMFi: bigint;
54
- readonly SupportsSetPeersExtendedMessage: bigint;
55
- readonly SupportsAPSync: bigint;
56
- readonly SupportsWoL: bigint;
57
- readonly SupportsWoL2: bigint;
58
- readonly SupportsHangdogRemoteControl: bigint;
59
- readonly SupportsAudioStreamConnectionSetup: bigint;
60
- readonly SupportsAudioMetadataControl: bigint;
61
- readonly SupportsRFC2198Redundancy: bigint;
62
- };
63
16
  /**
64
17
  * AirPlay feature flags as a bitmask of bigint values. Each flag corresponds to a specific
65
18
  * capability advertised by an AirPlay device in its mDNS TXT record "features" field.
66
- * The bit positions match Apple's internal AirPlayFeatureFlags enum.
19
+ *
20
+ * Bit positions are derived from Apple's internal `APSFeaturesSetFeature()` calls in the
21
+ * AirPlayReceiver framework (`sysInfo_createFeaturesInternal`). Flag names follow the
22
+ * community convention (pyatv, emanuelecozzi.net) with camelCase normalization.
23
+ *
24
+ * Sources:
25
+ * - Apple AirPlayReceiver framework decompilation (sysInfo.c)
26
+ * - pyatv AirPlayFlags enum (pyatv/protocols/airplay/utils.py)
27
+ * - https://emanuelecozzi.net/docs/airplay2/features/
67
28
  */
68
- declare const AirPlayFeatureFlags: AirPlayFeatureFlagsType;
69
- /** String union of all known AirPlay feature flag names. */
70
- type AirPlayFeatureFlagName = keyof typeof AirPlayFeatureFlags;
29
+ declare const AirPlayFeatureFlags: Record<string, bigint>;
30
+ /** String name of any known AirPlay feature flag. */
31
+ type AirPlayFeatureFlagName = string;
71
32
  /** The type of pairing required to connect to an AirPlay device. */
72
33
  type PairingRequirement = 'none' | 'pin' | 'transient' | 'homekit';
73
34
  /**
@@ -130,6 +91,21 @@ declare function isPasswordRequired(txt: Record<string, string>): boolean;
130
91
  * @returns True if remote control is supported (typically Apple TV only).
131
92
  */
132
93
  declare function isRemoteControlSupported(txt: Record<string, string>): boolean;
94
+ /**
95
+ * Feature bitmask advertised when connecting for remote control sessions.
96
+ *
97
+ * Includes media control, system pairing, encryption, volume, and
98
+ * hangdog remote control capabilities.
99
+ */
100
+ declare const SENDER_FEATURES_REMOTE_CONTROL: bigint;
101
+ /**
102
+ * Feature bitmask advertised when connecting for audio streaming sessions.
103
+ *
104
+ * Extends the remote control features with buffered audio, audio stream
105
+ * connection setup, metadata control, format negotiation, and PTP
106
+ * synchronization support.
107
+ */
108
+ declare const SENDER_FEATURES_AUDIO: bigint;
133
109
  //#endregion
134
110
  //#region src/reporter.d.ts
135
111
  /**
@@ -298,7 +274,12 @@ declare class AccessoryPair extends BasePairing {
298
274
  * @param context - Shared context for logging and device identity.
299
275
  * @param requestHandler - Callback that sends TLV8-encoded data and returns the response.
300
276
  */
301
- constructor(context: Context, requestHandler: RequestHandler);
277
+ /**
278
+ * @param context - Shared context for logging and device identity.
279
+ * @param requestHandler - Callback that sends TLV8-encoded data and returns the response.
280
+ * @param useAes - Use AES-128-CTR instead of ChaCha20-Poly1305 for M5/M6 encryption (legacy devices).
281
+ */
282
+ constructor(context: Context, requestHandler: RequestHandler, useAes?: boolean);
302
283
  /** Generates a new Ed25519 key pair for long-term identity. Must be called before pin() or transient(). */
303
284
  start(): Promise<void>;
304
285
  /**
@@ -385,8 +366,9 @@ declare class AccessoryVerify extends BasePairing {
385
366
  /**
386
367
  * @param context - Shared context for logging and device identity.
387
368
  * @param requestHandler - Callback that sends TLV8-encoded data and returns the response.
369
+ * @param useAes - Use AES-128-CTR instead of ChaCha20-Poly1305 for encryption (legacy devices).
388
370
  */
389
- constructor(context: Context, requestHandler: RequestHandler);
371
+ constructor(context: Context, requestHandler: RequestHandler, useAes?: boolean);
390
372
  /**
391
373
  * Performs the full pair-verify flow (M1 through M4) using stored credentials
392
374
  * from a previous pair-setup. Establishes a new session with forward secrecy
@@ -962,10 +944,20 @@ declare class EncryptionState {
962
944
  }
963
945
  //#endregion
964
946
  //#region src/const.d.ts
947
+ /** Default number of bytes per audio channel sample (16-bit PCM). */
948
+ declare const AUDIO_BYTES_PER_CHANNEL = 2;
949
+ /** Default number of audio channels (stereo). */
950
+ declare const AUDIO_CHANNELS = 2;
951
+ /** Number of PCM audio frames packed into a single RTP packet (ALAC/RAOP standard). */
952
+ declare const AUDIO_FRAMES_PER_PACKET = 352;
953
+ /** Default audio sample rate in Hz (CD quality). */
954
+ declare const AUDIO_SAMPLE_RATE = 44100;
965
955
  /** Default PIN used for transient (non-persistent) AirPlay pairing sessions. */
966
956
  declare const AIRPLAY_TRANSIENT_PIN = "3939";
967
957
  /** Timeout in milliseconds for HTTP requests during setup and control. */
968
958
  declare const HTTP_TIMEOUT = 6000;
959
+ /** Timeout in milliseconds for TCP socket connections during initial connect. Matches Apple's 30s default. */
960
+ declare const SOCKET_TIMEOUT = 30000;
969
961
  /** mDNS service type for AirPlay device discovery. */
970
962
  declare const AIRPLAY_SERVICE = "_airplay._tcp.local";
971
963
  /** mDNS service type for Companion Link (remote control protocol) device discovery. */
@@ -973,6 +965,26 @@ declare const COMPANION_LINK_SERVICE = "_companion-link._tcp.local";
973
965
  /** mDNS service type for RAOP (Remote Audio Output Protocol) device discovery. */
974
966
  declare const RAOP_SERVICE = "_raop._tcp.local";
975
967
  //#endregion
968
+ //#region src/hkdf.d.ts
969
+ /**
970
+ * Derives a pair of ChaCha20 encryption keys (read + write) from a shared secret
971
+ * using HKDF-SHA512 with direction-specific info strings.
972
+ *
973
+ * This is a shared helper used across AirPlay, Companion Link, and RAOP pairing
974
+ * flows to eliminate repeated HKDF boilerplate. The salt and info strings vary
975
+ * per protocol and stream type.
976
+ *
977
+ * @param sharedSecret - The shared secret from a pair-verify or pair-setup flow.
978
+ * @param salt - HKDF salt string (protocol-specific, e.g. 'Control-Salt').
979
+ * @param readInfo - HKDF info string for the read (decrypt) key.
980
+ * @param writeInfo - HKDF info string for the write (encrypt) key.
981
+ * @returns An object with `readKey` and `writeKey` as 32-byte Buffers.
982
+ */
983
+ declare function deriveEncryptionKeys(sharedSecret: Buffer, salt: string, readInfo: string, writeInfo: string): {
984
+ readKey: Buffer;
985
+ writeKey: Buffer;
986
+ };
987
+ //#endregion
976
988
  //#region src/symbols.d.ts
977
989
  /**
978
990
  * Unique symbol used as a key for accessing encryption state on connection instances.
@@ -1056,10 +1068,6 @@ declare class TimingServer {
1056
1068
  * @throws If the socket fails to bind.
1057
1069
  */
1058
1070
  listen(): Promise<void>;
1059
- /**
1060
- * Handles the socket 'connect' event by configuring buffer sizes.
1061
- */
1062
- onConnect(): void;
1063
1071
  /**
1064
1072
  * Handles socket errors by logging them.
1065
1073
  *
@@ -1167,42 +1175,42 @@ declare enum DeviceType {
1167
1175
  * @param identifier - Model identifier (e.g. "AppleTV6,2") or internal name (e.g. "J305AP").
1168
1176
  * @returns The matching device model, or {@link DeviceModel.Unknown} if unrecognized.
1169
1177
  */
1170
- declare const lookupDeviceModel: (identifier: string) => DeviceModel;
1178
+ declare function lookupDeviceModel(identifier: string): DeviceModel;
1171
1179
  /**
1172
1180
  * Returns the human-readable display name for a device model.
1173
1181
  *
1174
1182
  * @param model - The device model to look up.
1175
1183
  * @returns A display name like "Apple TV 4K (2nd generation)", or "Unknown".
1176
1184
  */
1177
- declare const getDeviceModelName: (model: DeviceModel) => string;
1185
+ declare function getDeviceModelName(model: DeviceModel): string;
1178
1186
  /**
1179
1187
  * Returns the high-level device type category for a device model.
1180
1188
  *
1181
1189
  * @param model - The device model to categorize.
1182
1190
  * @returns The device type (AppleTV, HomePod, AirPort, or Unknown).
1183
1191
  */
1184
- declare const getDeviceType: (model: DeviceModel) => DeviceType;
1192
+ declare function getDeviceType(model: DeviceModel): DeviceType;
1185
1193
  /**
1186
1194
  * Checks whether the given model is an Apple TV.
1187
1195
  *
1188
1196
  * @param model - The device model to check.
1189
1197
  * @returns True if the model is any Apple TV generation.
1190
1198
  */
1191
- declare const isAppleTV: (model: DeviceModel) => boolean;
1199
+ declare function isAppleTV(model: DeviceModel): boolean;
1192
1200
  /**
1193
1201
  * Checks whether the given model is a HomePod.
1194
1202
  *
1195
1203
  * @param model - The device model to check.
1196
1204
  * @returns True if the model is any HomePod variant.
1197
1205
  */
1198
- declare const isHomePod: (model: DeviceModel) => boolean;
1206
+ declare function isHomePod(model: DeviceModel): boolean;
1199
1207
  /**
1200
1208
  * Checks whether the given model is an AirPort Express.
1201
1209
  *
1202
1210
  * @param model - The device model to check.
1203
1211
  * @returns True if the model is any AirPort Express generation.
1204
1212
  */
1205
- declare const isAirPort: (model: DeviceModel) => boolean;
1213
+ declare function isAirPort(model: DeviceModel): boolean;
1206
1214
  //#endregion
1207
1215
  //#region src/errors.d.ts
1208
1216
  /**
@@ -1315,4 +1323,4 @@ interface AudioSource {
1315
1323
  stop(): Promise<void>;
1316
1324
  }
1317
1325
  //#endregion
1318
- export { AIRPLAY_SERVICE, AIRPLAY_TRANSIENT_PIN, type AccessoryCredentials, type AccessoryKeys, AccessoryPair, AccessoryVerify, type AirPlayFeatureFlagName, AirPlayFeatureFlags, AppleProtocolError, type AudioSource, AuthenticationError, COMPANION_LINK_SERVICE, type CombinedDiscoveryResult, CommandError, Connection, ConnectionClosedError, ConnectionError, ConnectionRecovery, type ConnectionRecoveryOptions, type ConnectionState, ConnectionTimeoutError, Context, CredentialsError, type DeviceIdentity, DeviceModel, DeviceType, Discovery, DiscoveryError, type DiscoveryResult, ENCRYPTION, EncryptionAwareConnection, EncryptionError, EncryptionState, type EventMap, HTTP_TIMEOUT, InvalidResponseError, JsonStorage, type Logger, type MdnsService, MemoryStorage, PairingError, type PairingRequirement, PlaybackError, type ProtocolType, RAOP_SERVICE, type Reporter, SetupError, Storage, type StorageData, type StoredDevice, TimeoutError, TimingServer, describeFlags, generateActiveRemoteId, generateDacpId, generateSessionId, getDeviceModelName, getDeviceType, getLocalIP, getMacAddress, getPairingRequirement, getProtocolVersion, hasFeatureFlag, isAirPort, isAppleTV, isHomePod, isPasswordRequired, isRemoteControlSupported, lookupDeviceModel, multicast as mdnsMulticast, unicast as mdnsUnicast, parseFeatures, prompt, randomInt32, randomInt64, reporter, uint16ToBE, uint53ToLE, v4 as uuid, waitFor };
1326
+ export { AIRPLAY_SERVICE, AIRPLAY_TRANSIENT_PIN, AUDIO_BYTES_PER_CHANNEL, AUDIO_CHANNELS, AUDIO_FRAMES_PER_PACKET, AUDIO_SAMPLE_RATE, type AccessoryCredentials, type AccessoryKeys, AccessoryPair, AccessoryVerify, type AirPlayFeatureFlagName, AirPlayFeatureFlags, AppleProtocolError, type AudioSource, AuthenticationError, COMPANION_LINK_SERVICE, type CombinedDiscoveryResult, CommandError, Connection, ConnectionClosedError, ConnectionError, ConnectionRecovery, type ConnectionRecoveryOptions, type ConnectionState, ConnectionTimeoutError, Context, CredentialsError, type DeviceIdentity, DeviceModel, DeviceType, Discovery, DiscoveryError, type DiscoveryResult, ENCRYPTION, EncryptionAwareConnection, EncryptionError, EncryptionState, type EventMap, HTTP_TIMEOUT, InvalidResponseError, JsonStorage, type Logger, type MdnsService, MemoryStorage, PairingError, type PairingRequirement, PlaybackError, type ProtocolType, RAOP_SERVICE, type Reporter, SENDER_FEATURES_AUDIO, SENDER_FEATURES_REMOTE_CONTROL, SOCKET_TIMEOUT, SetupError, Storage, type StorageData, type StoredDevice, TimeoutError, TimingServer, deriveEncryptionKeys, describeFlags, generateActiveRemoteId, generateDacpId, generateSessionId, getDeviceModelName, getDeviceType, getLocalIP, getMacAddress, getPairingRequirement, getProtocolVersion, hasFeatureFlag, isAirPort, isAppleTV, isHomePod, isPasswordRequired, isRemoteControlSupported, lookupDeviceModel, multicast as mdnsMulticast, unicast as mdnsUnicast, parseFeatures, prompt, randomInt32, randomInt64, reporter, uint16ToBE, uint53ToLE, v4 as uuid, waitFor };
package/dist/index.mjs CHANGED
@@ -2,13 +2,13 @@ import { createRequire } from "node:module";
2
2
  import { randomBytes, randomFillSync, randomUUID } from "node:crypto";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
- import { Socket, createConnection } from "node:net";
6
5
  import { createInterface } from "node:readline";
7
6
  import { createSocket } from "node:dgram";
7
+ import { Socket, createConnection } from "node:net";
8
8
  import { networkInterfaces } from "node:os";
9
9
  import { EventEmitter } from "node:events";
10
+ import { Aes, Chacha20, Curve25519, Ed25519, hkdf } from "@basmilius/apple-encryption";
10
11
  import { NTP, OPack, TLV8 } from "@basmilius/apple-encoding";
11
- import { Chacha20, Curve25519, Ed25519, hkdf } from "@basmilius/apple-encryption";
12
12
 
13
13
  //#region \0rolldown/runtime.js
14
14
  var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -64,7 +64,15 @@ function v4(options, buf, offset) {
64
64
  /**
65
65
  * AirPlay feature flags as a bitmask of bigint values. Each flag corresponds to a specific
66
66
  * capability advertised by an AirPlay device in its mDNS TXT record "features" field.
67
- * The bit positions match Apple's internal AirPlayFeatureFlags enum.
67
+ *
68
+ * Bit positions are derived from Apple's internal `APSFeaturesSetFeature()` calls in the
69
+ * AirPlayReceiver framework (`sysInfo_createFeaturesInternal`). Flag names follow the
70
+ * community convention (pyatv, emanuelecozzi.net) with camelCase normalization.
71
+ *
72
+ * Sources:
73
+ * - Apple AirPlayReceiver framework decompilation (sysInfo.c)
74
+ * - pyatv AirPlayFlags enum (pyatv/protocols/airplay/utils.py)
75
+ * - https://emanuelecozzi.net/docs/airplay2/features/
68
76
  */
69
77
  const AirPlayFeatureFlags = {
70
78
  SupportsAirPlayVideoV1: 1n << 0n,
@@ -76,22 +84,22 @@ const AirPlayFeatureFlags = {
76
84
  SupportsAirPlayScreen: 1n << 7n,
77
85
  SupportsAirPlayAudio: 1n << 9n,
78
86
  AudioRedundant: 1n << 11n,
79
- Authentication_4: 1n << 14n,
80
- MetadataFeatures_0: 1n << 15n,
81
- MetadataFeatures_1: 1n << 16n,
82
- MetadataFeatures_2: 1n << 17n,
83
- AudioFormats_0: 1n << 18n,
84
- AudioFormats_1: 1n << 19n,
85
- AudioFormats_2: 1n << 20n,
86
- AudioFormats_3: 1n << 21n,
87
- Authentication_1: 1n << 23n,
88
- Authentication_8: 1n << 26n,
87
+ Authentication4: 1n << 14n,
88
+ MetadataFeatures0: 1n << 15n,
89
+ MetadataFeatures1: 1n << 16n,
90
+ MetadataFeatures2: 1n << 17n,
91
+ AudioFormats0: 1n << 18n,
92
+ AudioFormats1: 1n << 19n,
93
+ AudioFormats2: 1n << 20n,
94
+ AudioFormats3: 1n << 21n,
95
+ Authentication1: 1n << 23n,
96
+ Authentication8: 1n << 26n,
89
97
  SupportsLegacyPairing: 1n << 27n,
90
98
  HasUnifiedAdvertiserInfo: 1n << 30n,
91
- IsCarPlay: 1n << 32n,
99
+ SupportsVolume: 1n << 32n,
92
100
  SupportsAirPlayVideoPlayQueue: 1n << 33n,
93
101
  SupportsAirPlayFromCloud: 1n << 34n,
94
- SupportsTLS_PSK: 1n << 35n,
102
+ SupportsTLSPSK: 1n << 35n,
95
103
  SupportsUnifiedMediaControl: 1n << 38n,
96
104
  SupportsBufferedAudio: 1n << 40n,
97
105
  SupportsPTP: 1n << 41n,
@@ -101,7 +109,7 @@ const AirPlayFeatureFlags = {
101
109
  SupportsHKPairingAndAccessControl: 1n << 46n,
102
110
  SupportsCoreUtilsPairingAndEncryption: 1n << 48n,
103
111
  SupportsAirPlayVideoV2: 1n << 49n,
104
- MetadataFeatures_3: 1n << 50n,
112
+ MetadataFeatures3: 1n << 50n,
105
113
  SupportsUnifiedPairSetupAndMFi: 1n << 51n,
106
114
  SupportsSetPeersExtendedMessage: 1n << 52n,
107
115
  SupportsAPSync: 1n << 54n,
@@ -144,7 +152,7 @@ function parseFeatures(features) {
144
152
  * @returns True if the flag is set.
145
153
  */
146
154
  function hasFeatureFlag(features, flag) {
147
- return (features & flag) !== 0n;
155
+ return (features & flag) === flag;
148
156
  }
149
157
  /**
150
158
  * Returns the names of all feature flags that are set in the given bitmask.
@@ -216,6 +224,21 @@ function isRemoteControlSupported(txt) {
216
224
  if (!featuresStr) return false;
217
225
  return hasFeatureFlag(parseFeatures(featuresStr), AirPlayFeatureFlags.SupportsHangdogRemoteControl);
218
226
  }
227
+ /**
228
+ * Feature bitmask advertised when connecting for remote control sessions.
229
+ *
230
+ * Includes media control, system pairing, encryption, volume, and
231
+ * hangdog remote control capabilities.
232
+ */
233
+ const SENDER_FEATURES_REMOTE_CONTROL = AirPlayFeatureFlags.SupportsAirPlayAudio | AirPlayFeatureFlags.AudioRedundant | AirPlayFeatureFlags.MetadataFeatures0 | AirPlayFeatureFlags.MetadataFeatures1 | AirPlayFeatureFlags.MetadataFeatures2 | AirPlayFeatureFlags.MetadataFeatures3 | AirPlayFeatureFlags.Authentication4 | AirPlayFeatureFlags.Authentication1 | AirPlayFeatureFlags.HasUnifiedAdvertiserInfo | AirPlayFeatureFlags.SupportsUnifiedMediaControl | AirPlayFeatureFlags.SupportsSystemPairing | AirPlayFeatureFlags.SupportsCoreUtilsPairingAndEncryption | AirPlayFeatureFlags.SupportsHKPairingAndAccessControl | AirPlayFeatureFlags.SupportsHangdogRemoteControl | AirPlayFeatureFlags.SupportsAPSync | AirPlayFeatureFlags.SupportsSetPeersExtendedMessage | AirPlayFeatureFlags.SupportsVolume;
234
+ /**
235
+ * Feature bitmask advertised when connecting for audio streaming sessions.
236
+ *
237
+ * Extends the remote control features with buffered audio, audio stream
238
+ * connection setup, metadata control, format negotiation, and PTP
239
+ * synchronization support.
240
+ */
241
+ const SENDER_FEATURES_AUDIO = SENDER_FEATURES_REMOTE_CONTROL | AirPlayFeatureFlags.SupportsBufferedAudio | AirPlayFeatureFlags.SupportsAudioStreamConnectionSetup | AirPlayFeatureFlags.SupportsAudioMetadataControl | AirPlayFeatureFlags.AudioFormats0 | AirPlayFeatureFlags.AudioFormats1 | AirPlayFeatureFlags.AudioFormats2 | AirPlayFeatureFlags.AudioFormats3 | AirPlayFeatureFlags.SupportsPTP;
219
242
 
220
243
  //#endregion
221
244
  //#region src/storage.ts
@@ -418,12 +441,20 @@ async function waitFor(ms) {
418
441
 
419
442
  //#endregion
420
443
  //#region src/const.ts
444
+ /** Default number of bytes per audio channel sample (16-bit PCM). */
445
+ const AUDIO_BYTES_PER_CHANNEL = 2;
446
+ /** Default number of audio channels (stereo). */
447
+ const AUDIO_CHANNELS = 2;
448
+ /** Number of PCM audio frames packed into a single RTP packet (ALAC/RAOP standard). */
449
+ const AUDIO_FRAMES_PER_PACKET = 352;
450
+ /** Default audio sample rate in Hz (CD quality). */
451
+ const AUDIO_SAMPLE_RATE = 44100;
421
452
  /** Default PIN used for transient (non-persistent) AirPlay pairing sessions. */
422
453
  const AIRPLAY_TRANSIENT_PIN = "3939";
423
454
  /** Timeout in milliseconds for HTTP requests during setup and control. */
424
455
  const HTTP_TIMEOUT = 6e3;
425
- /** Timeout in milliseconds for TCP socket connections during initial connect. */
426
- const SOCKET_TIMEOUT = 1e4;
456
+ /** Timeout in milliseconds for TCP socket connections during initial connect. Matches Apple's 30s default. */
457
+ const SOCKET_TIMEOUT = 3e4;
427
458
  /** mDNS service type for AirPlay device discovery. */
428
459
  const AIRPLAY_SERVICE = "_airplay._tcp.local";
429
460
  /** mDNS service type for Companion Link (remote control protocol) device discovery. */
@@ -788,6 +819,7 @@ const decodeSrvRecord = (buf, offset) => {
788
819
  */
789
820
  const decodeResource = (buf, offset) => {
790
821
  const [qname, nameEnd] = decodeQName(buf, offset);
822
+ if (nameEnd + 10 > buf.byteLength) return null;
791
823
  const qtype = buf.readUInt16BE(nameEnd);
792
824
  const qclass = buf.readUInt16BE(nameEnd + 2);
793
825
  const ttl = buf.readUInt32BE(nameEnd + 4);
@@ -832,7 +864,7 @@ const decodeResource = (buf, offset) => {
832
864
  * @param buf - The raw DNS response packet.
833
865
  * @returns The parsed header, answer records, and additional resource records.
834
866
  */
835
- const decodeDnsResponse = (buf) => {
867
+ function decodeDnsResponse(buf) {
836
868
  const header = decodeDnsHeader(buf);
837
869
  let offset = 12;
838
870
  for (let i = 0; i < header.qdcount; i++) {
@@ -841,17 +873,22 @@ const decodeDnsResponse = (buf) => {
841
873
  }
842
874
  const answers = [];
843
875
  for (let i = 0; i < header.ancount; i++) {
844
- const [record, newOffset] = decodeResource(buf, offset);
876
+ const result = decodeResource(buf, offset);
877
+ if (!result) break;
878
+ const [record, newOffset] = result;
845
879
  answers.push(record);
846
880
  offset = newOffset;
847
881
  }
848
882
  for (let i = 0; i < header.nscount; i++) {
849
- const [, newOffset] = decodeResource(buf, offset);
850
- offset = newOffset;
883
+ const result = decodeResource(buf, offset);
884
+ if (!result) break;
885
+ offset = result[1];
851
886
  }
852
887
  const resources = [];
853
888
  for (let i = 0; i < header.arcount; i++) {
854
- const [record, newOffset] = decodeResource(buf, offset);
889
+ const result = decodeResource(buf, offset);
890
+ if (!result) break;
891
+ const [record, newOffset] = result;
855
892
  resources.push(record);
856
893
  offset = newOffset;
857
894
  }
@@ -860,7 +897,7 @@ const decodeDnsResponse = (buf) => {
860
897
  answers,
861
898
  resources
862
899
  };
863
- };
900
+ }
864
901
  /**
865
902
  * Aggregates DNS records from multiple mDNS responses and resolves them
866
903
  * into complete service descriptions. Correlates PTR, SRV, TXT, and A records
@@ -927,7 +964,7 @@ var ServiceCollector = class {
927
964
  }
928
965
  };
929
966
  /** Well-known ports used to wake sleeping Apple devices via TCP SYN. */
930
- const WAKE_PORTS$1 = [
967
+ const WAKE_PORTS = [
931
968
  7e3,
932
969
  3689,
933
970
  49152,
@@ -940,8 +977,8 @@ const WAKE_PORTS$1 = [
940
977
  * @param address - The IP address of the device to wake.
941
978
  * @returns A promise that resolves when all knock attempts complete (success or failure).
942
979
  */
943
- const knock = (address) => {
944
- const promises = WAKE_PORTS$1.map((port) => new Promise((resolve) => {
980
+ function knock(address) {
981
+ const promises = WAKE_PORTS.map((port) => new Promise((resolve) => {
945
982
  const socket = createConnection({
946
983
  host: address,
947
984
  port,
@@ -961,7 +998,7 @@ const knock = (address) => {
961
998
  });
962
999
  }));
963
1000
  return Promise.all(promises).then(() => {});
964
- };
1001
+ }
965
1002
  /**
966
1003
  * Performs unicast DNS-SD queries to specific hosts. First wakes the devices
967
1004
  * via TCP knocking, then repeatedly sends DNS queries via UDP to port 5353 on
@@ -1274,13 +1311,6 @@ const reporter = new Reporter();
1274
1311
  //#region src/discovery.ts
1275
1312
  /** Cache time-to-live in milliseconds. */
1276
1313
  const CACHE_TTL = 3e4;
1277
- /** Ports used for wake-on-network "knocking" to wake sleeping Apple devices. */
1278
- const WAKE_PORTS = [
1279
- 7e3,
1280
- 3689,
1281
- 49152,
1282
- 32498
1283
- ];
1284
1314
  /**
1285
1315
  * Converts a raw mDNS service record into a {@link DiscoveryResult}.
1286
1316
  *
@@ -1393,27 +1423,8 @@ var Discovery = class Discovery {
1393
1423
  *
1394
1424
  * @param address - The IP address of the device to wake.
1395
1425
  */
1396
- static async wake(address) {
1397
- const promises = WAKE_PORTS.map((port) => new Promise((resolve) => {
1398
- const socket = createConnection({
1399
- host: address,
1400
- port,
1401
- timeout: 500
1402
- });
1403
- socket.on("connect", () => {
1404
- socket.destroy();
1405
- resolve();
1406
- });
1407
- socket.on("error", () => {
1408
- socket.destroy();
1409
- resolve();
1410
- });
1411
- socket.on("timeout", () => {
1412
- socket.destroy();
1413
- resolve();
1414
- });
1415
- }));
1416
- await Promise.all(promises);
1426
+ static wake(address) {
1427
+ return knock(address);
1417
1428
  }
1418
1429
  /**
1419
1430
  * Discovers all Apple devices on the network across all supported service types
@@ -1892,6 +1903,42 @@ var Context = class {
1892
1903
  }
1893
1904
  };
1894
1905
 
1906
+ //#endregion
1907
+ //#region src/hkdf.ts
1908
+ /**
1909
+ * Derives a pair of ChaCha20 encryption keys (read + write) from a shared secret
1910
+ * using HKDF-SHA512 with direction-specific info strings.
1911
+ *
1912
+ * This is a shared helper used across AirPlay, Companion Link, and RAOP pairing
1913
+ * flows to eliminate repeated HKDF boilerplate. The salt and info strings vary
1914
+ * per protocol and stream type.
1915
+ *
1916
+ * @param sharedSecret - The shared secret from a pair-verify or pair-setup flow.
1917
+ * @param salt - HKDF salt string (protocol-specific, e.g. 'Control-Salt').
1918
+ * @param readInfo - HKDF info string for the read (decrypt) key.
1919
+ * @param writeInfo - HKDF info string for the write (encrypt) key.
1920
+ * @returns An object with `readKey` and `writeKey` as 32-byte Buffers.
1921
+ */
1922
+ function deriveEncryptionKeys(sharedSecret, salt, readInfo, writeInfo) {
1923
+ const saltBuffer = Buffer.from(salt);
1924
+ return {
1925
+ readKey: hkdf({
1926
+ hash: "sha512",
1927
+ key: sharedSecret,
1928
+ length: 32,
1929
+ salt: saltBuffer,
1930
+ info: Buffer.from(readInfo)
1931
+ }),
1932
+ writeKey: hkdf({
1933
+ hash: "sha512",
1934
+ key: sharedSecret,
1935
+ length: 32,
1936
+ salt: saltBuffer,
1937
+ info: Buffer.from(writeInfo)
1938
+ })
1939
+ };
1940
+ }
1941
+
1895
1942
  //#endregion
1896
1943
  //#region ../../node_modules/.bun/fast-srp-hap@2.0.4/node_modules/fast-srp-hap/jsbn/jsbn.js
1897
1944
  var require_jsbn = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -3965,6 +4012,8 @@ var AccessoryPair = class extends BasePairing {
3965
4012
  #pairingId;
3966
4013
  /** Protocol-specific callback for sending TLV8-encoded pair-setup requests. */
3967
4014
  #requestHandler;
4015
+ /** Whether to use AES-128-CTR instead of ChaCha20-Poly1305 for M5/M6 encryption. */
4016
+ #useAes;
3968
4017
  /** Ed25519 public key for long-term identity. */
3969
4018
  #publicKey;
3970
4019
  /** Ed25519 secret key for signing identity proofs. */
@@ -3975,11 +4024,17 @@ var AccessoryPair = class extends BasePairing {
3975
4024
  * @param context - Shared context for logging and device identity.
3976
4025
  * @param requestHandler - Callback that sends TLV8-encoded data and returns the response.
3977
4026
  */
3978
- constructor(context, requestHandler) {
4027
+ /**
4028
+ * @param context - Shared context for logging and device identity.
4029
+ * @param requestHandler - Callback that sends TLV8-encoded data and returns the response.
4030
+ * @param useAes - Use AES-128-CTR instead of ChaCha20-Poly1305 for M5/M6 encryption (legacy devices).
4031
+ */
4032
+ constructor(context, requestHandler, useAes = false) {
3979
4033
  super(context);
3980
4034
  this.#name = "basmilius/apple-protocols";
3981
4035
  this.#pairingId = Buffer.from(v4().toUpperCase());
3982
4036
  this.#requestHandler = requestHandler;
4037
+ this.#useAes = useAes;
3983
4038
  }
3984
4039
  /** Generates a new Ed25519 key pair for long-term identity. Must be called before pin() or transient(). */
3985
4040
  async start() {
@@ -4020,20 +4075,7 @@ var AccessoryPair = class extends BasePairing {
4020
4075
  const m2 = await this.m2(m1);
4021
4076
  const m3 = await this.m3(m2);
4022
4077
  const m4 = await this.m4(m3);
4023
- const accessoryToControllerKey = hkdf({
4024
- hash: "sha512",
4025
- key: m4.sharedSecret,
4026
- length: 32,
4027
- salt: Buffer.from("Control-Salt"),
4028
- info: Buffer.from("Control-Read-Encryption-Key")
4029
- });
4030
- const controllerToAccessoryKey = hkdf({
4031
- hash: "sha512",
4032
- key: m4.sharedSecret,
4033
- length: 32,
4034
- salt: Buffer.from("Control-Salt"),
4035
- info: Buffer.from("Control-Write-Encryption-Key")
4036
- });
4078
+ const { readKey: accessoryToControllerKey, writeKey: controllerToAccessoryKey } = deriveEncryptionKeys(m4.sharedSecret, "Control-Salt", "Control-Read-Encryption-Key", "Control-Write-Encryption-Key");
4037
4079
  return {
4038
4080
  pairingId: this.#pairingId,
4039
4081
  sharedSecret: m4.sharedSecret,
@@ -4135,10 +4177,34 @@ var AccessoryPair = class extends BasePairing {
4135
4177
  [TLV8.Value.Signature, Buffer.from(signature)],
4136
4178
  [TLV8.Value.Name, OPack.encode({ name: this.#name })]
4137
4179
  ]);
4138
- const { authTag, ciphertext } = Chacha20.encrypt(sessionKey, Buffer.from("PS-Msg05"), null, innerTlv);
4139
- const encrypted = Buffer.concat([ciphertext, authTag]);
4180
+ let encrypted;
4181
+ if (this.#useAes) {
4182
+ const aesKey = hkdf({
4183
+ hash: "sha512",
4184
+ key: m4.sharedSecret,
4185
+ length: 16,
4186
+ salt: Buffer.alloc(0),
4187
+ info: Buffer.from("Pair-Setup-AES-Key")
4188
+ });
4189
+ const aesIv = hkdf({
4190
+ hash: "sha512",
4191
+ key: m4.sharedSecret,
4192
+ length: 16,
4193
+ salt: Buffer.alloc(0),
4194
+ info: Buffer.from("Pair-Setup-AES-IV")
4195
+ });
4196
+ encrypted = Aes.encrypt(aesKey, aesIv, innerTlv);
4197
+ } else {
4198
+ const { authTag, ciphertext } = Chacha20.encrypt(sessionKey, Buffer.from("PS-Msg05"), null, innerTlv);
4199
+ encrypted = Buffer.concat([ciphertext, authTag]);
4200
+ }
4140
4201
  const response = await this.#requestHandler("m5", TLV8.encode([[TLV8.Value.State, TLV8.State.M5], [TLV8.Value.EncryptedData, encrypted]]));
4141
4202
  const encryptedDataRaw = this.tlv(response).get(TLV8.Value.EncryptedData);
4203
+ if (this.#useAes) return {
4204
+ authTag: Buffer.alloc(0),
4205
+ data: encryptedDataRaw,
4206
+ sessionKey
4207
+ };
4142
4208
  const encryptedData = encryptedDataRaw.subarray(0, -16);
4143
4209
  return {
4144
4210
  authTag: encryptedDataRaw.subarray(-16),
@@ -4156,7 +4222,24 @@ var AccessoryPair = class extends BasePairing {
4156
4222
  * Returns the accessory's long-term public key and identifier for future Pair-Verify sessions.
4157
4223
  */
4158
4224
  async m6(m4, m5) {
4159
- const data = Chacha20.decrypt(m5.sessionKey, Buffer.from("PS-Msg06"), null, m5.data, m5.authTag);
4225
+ let data;
4226
+ if (this.#useAes) {
4227
+ const aesKey = hkdf({
4228
+ hash: "sha512",
4229
+ key: m4.sharedSecret,
4230
+ length: 16,
4231
+ salt: Buffer.alloc(0),
4232
+ info: Buffer.from("Pair-Setup-AES-Key")
4233
+ });
4234
+ const aesIv = hkdf({
4235
+ hash: "sha512",
4236
+ key: m4.sharedSecret,
4237
+ length: 16,
4238
+ salt: Buffer.alloc(0),
4239
+ info: Buffer.from("Pair-Setup-AES-IV")
4240
+ });
4241
+ data = Aes.decrypt(aesKey, aesIv, m5.data);
4242
+ } else data = Chacha20.decrypt(m5.sessionKey, Buffer.from("PS-Msg06"), null, m5.data, m5.authTag);
4160
4243
  const tlv = TLV8.decode(data);
4161
4244
  const accessoryIdentifier = tlv.get(TLV8.Value.Identifier);
4162
4245
  const accessoryLongTermPublicKey = tlv.get(TLV8.Value.PublicKey);
@@ -4197,14 +4280,18 @@ var AccessoryVerify = class extends BasePairing {
4197
4280
  #ephemeralKeyPair;
4198
4281
  /** Protocol-specific callback for sending TLV8-encoded pair-verify requests. */
4199
4282
  #requestHandler;
4283
+ /** Whether to use AES-128-CTR instead of ChaCha20-Poly1305 for encryption. */
4284
+ #useAes;
4200
4285
  /**
4201
4286
  * @param context - Shared context for logging and device identity.
4202
4287
  * @param requestHandler - Callback that sends TLV8-encoded data and returns the response.
4288
+ * @param useAes - Use AES-128-CTR instead of ChaCha20-Poly1305 for encryption (legacy devices).
4203
4289
  */
4204
- constructor(context, requestHandler) {
4290
+ constructor(context, requestHandler, useAes = false) {
4205
4291
  super(context);
4206
4292
  this.#ephemeralKeyPair = Curve25519.generateKeyPair();
4207
4293
  this.#requestHandler = requestHandler;
4294
+ this.#useAes = useAes;
4208
4295
  }
4209
4296
  /**
4210
4297
  * Performs the full pair-verify flow (M1 through M4) using stored credentials
@@ -4255,9 +4342,28 @@ var AccessoryVerify = class extends BasePairing {
4255
4342
  salt: Buffer.from("Pair-Verify-Encrypt-Salt"),
4256
4343
  info: Buffer.from("Pair-Verify-Encrypt-Info")
4257
4344
  });
4258
- const encryptedData = m1.encryptedData.subarray(0, -16);
4259
- const encryptedTag = m1.encryptedData.subarray(-16);
4260
- const data = Chacha20.decrypt(sessionKey, Buffer.from("PV-Msg02"), null, encryptedData, encryptedTag);
4345
+ let data;
4346
+ if (this.#useAes) {
4347
+ const aesKey = hkdf({
4348
+ hash: "sha512",
4349
+ key: sharedSecret,
4350
+ length: 16,
4351
+ salt: Buffer.alloc(0),
4352
+ info: Buffer.from("Pair-Verify-AES-Key")
4353
+ });
4354
+ const aesIv = hkdf({
4355
+ hash: "sha512",
4356
+ key: sharedSecret,
4357
+ length: 16,
4358
+ salt: Buffer.alloc(0),
4359
+ info: Buffer.from("Pair-Verify-AES-IV")
4360
+ });
4361
+ data = Aes.decrypt(aesKey, aesIv, m1.encryptedData);
4362
+ } else {
4363
+ const encryptedData = m1.encryptedData.subarray(0, -16);
4364
+ const encryptedTag = m1.encryptedData.subarray(-16);
4365
+ data = Chacha20.decrypt(sessionKey, Buffer.from("PV-Msg02"), null, encryptedData, encryptedTag);
4366
+ }
4261
4367
  const tlv = TLV8.decode(data);
4262
4368
  const accessoryIdentifier = tlv.get(TLV8.Value.Identifier);
4263
4369
  const accessorySignature = tlv.get(TLV8.Value.Signature);
@@ -4289,8 +4395,27 @@ var AccessoryVerify = class extends BasePairing {
4289
4395
  ]);
4290
4396
  const iosDeviceSignature = Buffer.from(Ed25519.sign(iosDeviceInfo, secretKey));
4291
4397
  const innerTlv = TLV8.encode([[TLV8.Value.Identifier, pairingId], [TLV8.Value.Signature, iosDeviceSignature]]);
4292
- const { authTag, ciphertext } = Chacha20.encrypt(m2.sessionKey, Buffer.from("PV-Msg03"), null, innerTlv);
4293
- const encrypted = Buffer.concat([ciphertext, authTag]);
4398
+ let encrypted;
4399
+ if (this.#useAes) {
4400
+ const aesKey = hkdf({
4401
+ hash: "sha512",
4402
+ key: m2.sharedSecret,
4403
+ length: 16,
4404
+ salt: Buffer.alloc(0),
4405
+ info: Buffer.from("Pair-Verify-AES-Key")
4406
+ });
4407
+ const aesIv = hkdf({
4408
+ hash: "sha512",
4409
+ key: m2.sharedSecret,
4410
+ length: 16,
4411
+ salt: Buffer.alloc(0),
4412
+ info: Buffer.from("Pair-Verify-AES-IV")
4413
+ });
4414
+ encrypted = Aes.encrypt(aesKey, aesIv, innerTlv);
4415
+ } else {
4416
+ const { authTag, ciphertext } = Chacha20.encrypt(m2.sessionKey, Buffer.from("PV-Msg03"), null, innerTlv);
4417
+ encrypted = Buffer.concat([ciphertext, authTag]);
4418
+ }
4294
4419
  await this.#requestHandler("m3", TLV8.encode([[TLV8.Value.State, TLV8.State.M3], [TLV8.Value.EncryptedData, encrypted]]));
4295
4420
  return {};
4296
4421
  }
@@ -4488,16 +4613,16 @@ var TimingServer = class {
4488
4613
  constructor() {
4489
4614
  this.#logger = new Logger("timing-server");
4490
4615
  this.#socket = createSocket("udp4");
4491
- this.onConnect = this.onConnect.bind(this);
4492
4616
  this.onError = this.onError.bind(this);
4493
4617
  this.onMessage = this.onMessage.bind(this);
4494
- this.#socket.on("connect", this.onConnect);
4495
4618
  this.#socket.on("error", this.onError);
4496
4619
  this.#socket.on("message", this.onMessage);
4497
4620
  }
4498
4621
  /** Closes the UDP socket and resets the port. */
4499
4622
  close() {
4500
- this.#socket.close();
4623
+ try {
4624
+ this.#socket.close();
4625
+ } catch {}
4501
4626
  this.#port = 0;
4502
4627
  }
4503
4628
  /**
@@ -4523,13 +4648,6 @@ var TimingServer = class {
4523
4648
  });
4524
4649
  }
4525
4650
  /**
4526
- * Handles the socket 'connect' event by configuring buffer sizes.
4527
- */
4528
- onConnect() {
4529
- this.#socket.setRecvBufferSize(16384);
4530
- this.#socket.setSendBufferSize(16384);
4531
- }
4532
- /**
4533
4651
  * Handles socket errors by logging them.
4534
4652
  *
4535
4653
  * @param err - The error that occurred.
@@ -4589,7 +4707,7 @@ var TimingServer = class {
4589
4707
  * @returns A random 32-bit unsigned integer as a decimal string.
4590
4708
  */
4591
4709
  function generateActiveRemoteId() {
4592
- return Math.floor(Math.random() * 2 ** 32).toString(10);
4710
+ return randomBytes(4).readUInt32BE(0).toString(10);
4593
4711
  }
4594
4712
  /**
4595
4713
  * Generates a random DACP-ID for identifying this controller in DACP sessions.
@@ -4597,7 +4715,7 @@ function generateActiveRemoteId() {
4597
4715
  * @returns A random 64-bit integer as an uppercase hexadecimal string.
4598
4716
  */
4599
4717
  function generateDacpId() {
4600
- return Math.floor(Math.random() * 2 ** 64).toString(16).toUpperCase();
4718
+ return randomBytes(8).toString("hex").toUpperCase();
4601
4719
  }
4602
4720
  /**
4603
4721
  * Generates a random session identifier for RTSP and AirPlay sessions.
@@ -4605,7 +4723,7 @@ function generateDacpId() {
4605
4723
  * @returns A random 32-bit unsigned integer as a decimal string.
4606
4724
  */
4607
4725
  function generateSessionId() {
4608
- return Math.floor(Math.random() * 2 ** 32).toString(10);
4726
+ return randomBytes(4).readUInt32BE(0).toString(10);
4609
4727
  }
4610
4728
  /**
4611
4729
  * Finds the first non-internal IPv4 address on this machine.
@@ -4789,42 +4907,54 @@ const MODEL_TYPES = {
4789
4907
  * @param identifier - Model identifier (e.g. "AppleTV6,2") or internal name (e.g. "J305AP").
4790
4908
  * @returns The matching device model, or {@link DeviceModel.Unknown} if unrecognized.
4791
4909
  */
4792
- const lookupDeviceModel = (identifier) => MODEL_IDENTIFIERS[identifier] ?? INTERNAL_NAMES[identifier] ?? DeviceModel.Unknown;
4910
+ function lookupDeviceModel(identifier) {
4911
+ return MODEL_IDENTIFIERS[identifier] ?? INTERNAL_NAMES[identifier] ?? DeviceModel.Unknown;
4912
+ }
4793
4913
  /**
4794
4914
  * Returns the human-readable display name for a device model.
4795
4915
  *
4796
4916
  * @param model - The device model to look up.
4797
4917
  * @returns A display name like "Apple TV 4K (2nd generation)", or "Unknown".
4798
4918
  */
4799
- const getDeviceModelName = (model) => MODEL_NAMES[model] ?? "Unknown";
4919
+ function getDeviceModelName(model) {
4920
+ return MODEL_NAMES[model] ?? "Unknown";
4921
+ }
4800
4922
  /**
4801
4923
  * Returns the high-level device type category for a device model.
4802
4924
  *
4803
4925
  * @param model - The device model to categorize.
4804
4926
  * @returns The device type (AppleTV, HomePod, AirPort, or Unknown).
4805
4927
  */
4806
- const getDeviceType = (model) => MODEL_TYPES[model] ?? DeviceType.Unknown;
4928
+ function getDeviceType(model) {
4929
+ return MODEL_TYPES[model] ?? DeviceType.Unknown;
4930
+ }
4807
4931
  /**
4808
4932
  * Checks whether the given model is an Apple TV.
4809
4933
  *
4810
4934
  * @param model - The device model to check.
4811
4935
  * @returns True if the model is any Apple TV generation.
4812
4936
  */
4813
- const isAppleTV = (model) => getDeviceType(model) === DeviceType.AppleTV;
4937
+ function isAppleTV(model) {
4938
+ return getDeviceType(model) === DeviceType.AppleTV;
4939
+ }
4814
4940
  /**
4815
4941
  * Checks whether the given model is a HomePod.
4816
4942
  *
4817
4943
  * @param model - The device model to check.
4818
4944
  * @returns True if the model is any HomePod variant.
4819
4945
  */
4820
- const isHomePod = (model) => getDeviceType(model) === DeviceType.HomePod;
4946
+ function isHomePod(model) {
4947
+ return getDeviceType(model) === DeviceType.HomePod;
4948
+ }
4821
4949
  /**
4822
4950
  * Checks whether the given model is an AirPort Express.
4823
4951
  *
4824
4952
  * @param model - The device model to check.
4825
4953
  * @returns True if the model is any AirPort Express generation.
4826
4954
  */
4827
- const isAirPort = (model) => getDeviceType(model) === DeviceType.AirPort;
4955
+ function isAirPort(model) {
4956
+ return getDeviceType(model) === DeviceType.AirPort;
4957
+ }
4828
4958
 
4829
4959
  //#endregion
4830
- export { AIRPLAY_SERVICE, AIRPLAY_TRANSIENT_PIN, AccessoryPair, AccessoryVerify, AirPlayFeatureFlags, AppleProtocolError, AuthenticationError, COMPANION_LINK_SERVICE, CommandError, Connection, ConnectionClosedError, ConnectionError, ConnectionRecovery, ConnectionTimeoutError, Context, CredentialsError, DeviceModel, DeviceType, Discovery, DiscoveryError, ENCRYPTION, EncryptionAwareConnection, EncryptionError, EncryptionState, HTTP_TIMEOUT, InvalidResponseError, JsonStorage, MemoryStorage, PairingError, PlaybackError, RAOP_SERVICE, SetupError, Storage, TimeoutError, TimingServer, describeFlags, generateActiveRemoteId, generateDacpId, generateSessionId, getDeviceModelName, getDeviceType, getLocalIP, getMacAddress, getPairingRequirement, getProtocolVersion, hasFeatureFlag, isAirPort, isAppleTV, isHomePod, isPasswordRequired, isRemoteControlSupported, lookupDeviceModel, multicast as mdnsMulticast, unicast as mdnsUnicast, parseFeatures, prompt, randomInt32, randomInt64, reporter, uint16ToBE, uint53ToLE, v4 as uuid, waitFor };
4960
+ export { AIRPLAY_SERVICE, AIRPLAY_TRANSIENT_PIN, AUDIO_BYTES_PER_CHANNEL, AUDIO_CHANNELS, AUDIO_FRAMES_PER_PACKET, AUDIO_SAMPLE_RATE, AccessoryPair, AccessoryVerify, AirPlayFeatureFlags, AppleProtocolError, AuthenticationError, COMPANION_LINK_SERVICE, CommandError, Connection, ConnectionClosedError, ConnectionError, ConnectionRecovery, ConnectionTimeoutError, Context, CredentialsError, DeviceModel, DeviceType, Discovery, DiscoveryError, ENCRYPTION, EncryptionAwareConnection, EncryptionError, EncryptionState, HTTP_TIMEOUT, InvalidResponseError, JsonStorage, MemoryStorage, PairingError, PlaybackError, RAOP_SERVICE, SENDER_FEATURES_AUDIO, SENDER_FEATURES_REMOTE_CONTROL, SOCKET_TIMEOUT, SetupError, Storage, TimeoutError, TimingServer, deriveEncryptionKeys, describeFlags, generateActiveRemoteId, generateDacpId, generateSessionId, getDeviceModelName, getDeviceType, getLocalIP, getMacAddress, getPairingRequirement, getProtocolVersion, hasFeatureFlag, isAirPort, isAppleTV, isHomePod, isPasswordRequired, isRemoteControlSupported, lookupDeviceModel, multicast as mdnsMulticast, unicast as mdnsUnicast, parseFeatures, prompt, randomInt32, randomInt64, reporter, uint16ToBE, uint53ToLE, v4 as uuid, waitFor };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@basmilius/apple-common",
3
3
  "description": "Common features shared across various apple protocol packages.",
4
- "version": "0.10.1",
4
+ "version": "0.12.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -45,14 +45,14 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@basmilius/apple-encoding": "0.10.1",
49
- "@basmilius/apple-encryption": "0.10.1"
48
+ "@basmilius/apple-encoding": "0.12.0",
49
+ "@basmilius/apple-encryption": "0.12.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/bun": "^1.3.11",
53
53
  "@types/node": "^25.5.0",
54
54
  "fast-srp-hap": "^2.0.4",
55
- "tsdown": "^0.21.4",
55
+ "tsdown": "^0.21.6",
56
56
  "uuid": "^13.0.0"
57
57
  }
58
58
  }