@bharper/atv-js 0.2.4 → 0.3.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.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +136 -6
- package/dist/index.js.map +1 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +17 -2
- package/dist/mdns.js.map +1 -1
- package/examples/print-device-json.js +22 -0
- package/package.json +2 -3
- package/src/index.ts +179 -8
- package/src/mdns.ts +18 -2
package/dist/index.d.ts
CHANGED
|
@@ -45,9 +45,24 @@ export interface AppleTVConnection {
|
|
|
45
45
|
protocol: CompanionProtocol;
|
|
46
46
|
device: AppleTVDevice;
|
|
47
47
|
credentials: Credentials;
|
|
48
|
+
/** Credentials + endpoint used for this connection attempt/result. */
|
|
49
|
+
usedCredentials: ConnectionCredentialsSnapshot;
|
|
50
|
+
/** True when usedCredentials exactly match the values passed to connect(...). */
|
|
51
|
+
usedCredentialsMatchProvided: boolean;
|
|
48
52
|
/** @internal */
|
|
49
53
|
_keyboardFocusState: KeyboardFocusState;
|
|
50
54
|
}
|
|
55
|
+
export interface ConnectionCredentialsSnapshot {
|
|
56
|
+
airplay: string;
|
|
57
|
+
companion: string;
|
|
58
|
+
device: {
|
|
59
|
+
name: string;
|
|
60
|
+
address: string;
|
|
61
|
+
port: number;
|
|
62
|
+
airplayPort: number;
|
|
63
|
+
identifier: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
51
66
|
/**
|
|
52
67
|
* Connect to an Apple TV using stored credentials.
|
|
53
68
|
* Performs pair-verify and sets up encrypted Companion channel.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ Object.defineProperty(exports, "HidCommand", { enumerable: true, get: function (
|
|
|
28
28
|
Object.defineProperty(exports, "MediaControlCommand", { enumerable: true, get: function () { return remote_1.MediaControlCommand; } });
|
|
29
29
|
var keyboard_1 = require("./companion/keyboard");
|
|
30
30
|
Object.defineProperty(exports, "KeyboardFocusState", { enumerable: true, get: function () { return keyboard_1.KeyboardFocusState; } });
|
|
31
|
+
const mdns_2 = require("./mdns");
|
|
31
32
|
const connection_1 = require("./companion/connection");
|
|
32
33
|
const protocol_1 = require("./companion/protocol");
|
|
33
34
|
const auth_1 = require("./companion/auth");
|
|
@@ -88,16 +89,142 @@ async function finishCompanionPairing(session, pin, displayName) {
|
|
|
88
89
|
}
|
|
89
90
|
return creds;
|
|
90
91
|
}
|
|
92
|
+
function errorMessage(error) {
|
|
93
|
+
if (error instanceof Error)
|
|
94
|
+
return error.message;
|
|
95
|
+
return String(error);
|
|
96
|
+
}
|
|
97
|
+
const ID_PROPERTY_KEYS = new Set(['rpmrtid', 'deviceid', 'macaddress']);
|
|
98
|
+
function normalizeIdentifier(value) {
|
|
99
|
+
return value.trim().toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
function getIdentifiers(device) {
|
|
102
|
+
const identifiers = new Set();
|
|
103
|
+
if (device.identifier) {
|
|
104
|
+
identifiers.add(normalizeIdentifier(device.identifier));
|
|
105
|
+
}
|
|
106
|
+
for (const [key, value] of Object.entries(device.properties || {})) {
|
|
107
|
+
if (!value || !ID_PROPERTY_KEYS.has(key.toLowerCase()))
|
|
108
|
+
continue;
|
|
109
|
+
identifiers.add(normalizeIdentifier(value));
|
|
110
|
+
}
|
|
111
|
+
return identifiers;
|
|
112
|
+
}
|
|
113
|
+
function sharesIdentifier(target, candidate) {
|
|
114
|
+
const targetIds = getIdentifiers(target);
|
|
115
|
+
if (targetIds.size === 0)
|
|
116
|
+
return false;
|
|
117
|
+
for (const identifier of getIdentifiers(candidate)) {
|
|
118
|
+
if (targetIds.has(identifier))
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
function deviceMatchScore(target, candidate) {
|
|
124
|
+
if (sharesIdentifier(target, candidate))
|
|
125
|
+
return 100;
|
|
126
|
+
if (candidate.address === target.address)
|
|
127
|
+
return 10;
|
|
128
|
+
if (candidate.name === target.name)
|
|
129
|
+
return 1;
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
function mergeDiscoveredDevice(target, discovered) {
|
|
133
|
+
return {
|
|
134
|
+
...target,
|
|
135
|
+
...discovered,
|
|
136
|
+
properties: { ...target.properties, ...discovered.properties },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function hasCompanionEndpointChanged(current, discovered) {
|
|
140
|
+
return current.address !== discovered.address || current.port !== discovered.port;
|
|
141
|
+
}
|
|
142
|
+
function toCredentialSnapshot(device, credentials) {
|
|
143
|
+
return {
|
|
144
|
+
airplay: credentials.airplay,
|
|
145
|
+
companion: credentials.companion,
|
|
146
|
+
device: {
|
|
147
|
+
name: device.name,
|
|
148
|
+
address: device.address,
|
|
149
|
+
port: device.port,
|
|
150
|
+
airplayPort: device.airplayPort,
|
|
151
|
+
identifier: device.identifier,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function snapshotsEqual(left, right) {
|
|
156
|
+
return left.airplay === right.airplay
|
|
157
|
+
&& left.companion === right.companion
|
|
158
|
+
&& left.device.name === right.device.name
|
|
159
|
+
&& left.device.address === right.device.address
|
|
160
|
+
&& left.device.port === right.device.port
|
|
161
|
+
&& left.device.airplayPort === right.device.airplayPort
|
|
162
|
+
&& left.device.identifier === right.device.identifier;
|
|
163
|
+
}
|
|
164
|
+
async function connectCompanion(device, companionCredentials) {
|
|
165
|
+
const connection = new connection_1.CompanionConnection(device.address, device.port);
|
|
166
|
+
const protocol = new protocol_1.CompanionProtocol(connection, companionCredentials);
|
|
167
|
+
connection.setListener(protocol);
|
|
168
|
+
await protocol.start();
|
|
169
|
+
return protocol;
|
|
170
|
+
}
|
|
171
|
+
async function discoverLatestDevice(device) {
|
|
172
|
+
const discovered = await (0, mdns_2.scan)(3000, false);
|
|
173
|
+
let bestMatch = null;
|
|
174
|
+
let bestScore = 0;
|
|
175
|
+
for (const candidate of discovered) {
|
|
176
|
+
const score = deviceMatchScore(device, candidate);
|
|
177
|
+
if (score === 0)
|
|
178
|
+
continue;
|
|
179
|
+
if (!bestMatch || score > bestScore) {
|
|
180
|
+
bestMatch = candidate;
|
|
181
|
+
bestScore = score;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (score === bestScore) {
|
|
185
|
+
const candidatePortMatches = candidate.port === device.port;
|
|
186
|
+
const currentPortMatches = bestMatch.port === device.port;
|
|
187
|
+
if (candidatePortMatches && !currentPortMatches) {
|
|
188
|
+
bestMatch = candidate;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return bestMatch;
|
|
193
|
+
}
|
|
91
194
|
/**
|
|
92
195
|
* Connect to an Apple TV using stored credentials.
|
|
93
196
|
* Performs pair-verify and sets up encrypted Companion channel.
|
|
94
197
|
*/
|
|
95
198
|
async function connect(device, credentials) {
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
199
|
+
let activeDevice = device;
|
|
200
|
+
const providedCredentials = toCredentialSnapshot(device, credentials);
|
|
201
|
+
let protocol;
|
|
202
|
+
try {
|
|
203
|
+
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
204
|
+
}
|
|
205
|
+
catch (initialError) {
|
|
206
|
+
let discovered = null;
|
|
207
|
+
try {
|
|
208
|
+
discovered = await discoverLatestDevice(activeDevice);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
discovered = null;
|
|
212
|
+
}
|
|
213
|
+
if (!discovered) {
|
|
214
|
+
throw initialError;
|
|
215
|
+
}
|
|
216
|
+
const mergedDevice = mergeDiscoveredDevice(activeDevice, discovered);
|
|
217
|
+
if (!hasCompanionEndpointChanged(activeDevice, mergedDevice)) {
|
|
218
|
+
throw initialError;
|
|
219
|
+
}
|
|
220
|
+
activeDevice = mergedDevice;
|
|
221
|
+
try {
|
|
222
|
+
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
223
|
+
}
|
|
224
|
+
catch (retryError) {
|
|
225
|
+
throw new Error(`Companion connection failed on saved endpoint ${device.address}:${device.port} and discovered endpoint ${activeDevice.address}:${activeDevice.port}: ${errorMessage(retryError)}`, { cause: initialError instanceof Error ? initialError : undefined });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
101
228
|
// Post-connection initialization (order matters!)
|
|
102
229
|
// 1. Send system info
|
|
103
230
|
// Client ID is stored hex-encoded in credentials, decode to get the actual UUID bytes
|
|
@@ -133,10 +260,13 @@ async function connect(device, credentials) {
|
|
|
133
260
|
protocol.subscribeEvent('_iMC');
|
|
134
261
|
const conn = {
|
|
135
262
|
protocol,
|
|
136
|
-
device,
|
|
263
|
+
device: activeDevice,
|
|
137
264
|
credentials,
|
|
265
|
+
usedCredentials: toCredentialSnapshot(activeDevice, credentials),
|
|
266
|
+
usedCredentialsMatchProvided: false,
|
|
138
267
|
_keyboardFocusState: keyboard_2.KeyboardFocusState.Unknown,
|
|
139
268
|
};
|
|
269
|
+
conn.usedCredentialsMatchProvided = snapshotsEqual(conn.usedCredentials, providedCredentials);
|
|
140
270
|
// Watch keyboard focus state
|
|
141
271
|
(0, keyboard_2.watchKeyboardFocus)(protocol, (state) => {
|
|
142
272
|
conn._keyboardFocusState = state;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAkDH,kDAGC;AAKD,oDASC;AAMD,sDAeC;AAKD,wDAgBC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/mdns.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdns.d.ts","sourceRoot":"","sources":["../src/mdns.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;
|
|
1
|
+
{"version":3,"file":"mdns.d.ts","sourceRoot":"","sources":["../src/mdns.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAgCD,wBAAsB,IAAI,CAAC,OAAO,SAAO,EAAE,WAAW,UAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAiKvF"}
|
package/dist/mdns.js
CHANGED
|
@@ -24,6 +24,14 @@ function isAppleTvModel(model) {
|
|
|
24
24
|
return true;
|
|
25
25
|
return APPLE_TV_MODELS.has(model);
|
|
26
26
|
}
|
|
27
|
+
function getPropertyIgnoreCase(properties, key) {
|
|
28
|
+
const expected = key.toLowerCase();
|
|
29
|
+
for (const [property, value] of Object.entries(properties)) {
|
|
30
|
+
if (property.toLowerCase() === expected)
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
27
35
|
async function scan(timeout = 5000, onlyAppleTV = true) {
|
|
28
36
|
return new Promise((resolve) => {
|
|
29
37
|
const browser = (0, multicast_dns_1.default)();
|
|
@@ -145,15 +153,22 @@ async function scan(timeout = 5000, onlyAppleTV = true) {
|
|
|
145
153
|
continue;
|
|
146
154
|
const allProps = { ...companion.properties, ...airplay?.properties };
|
|
147
155
|
// Model comes from _device-info._tcp.local service, not companion/airplay
|
|
148
|
-
const model = deviceInfoModels.get(name)
|
|
156
|
+
const model = deviceInfoModels.get(name)
|
|
157
|
+
|| getPropertyIgnoreCase(allProps, 'model')
|
|
158
|
+
|| getPropertyIgnoreCase(allProps, 'rpmd')
|
|
159
|
+
|| '';
|
|
149
160
|
if (onlyAppleTV && !isAppleTvModel(model))
|
|
150
161
|
continue;
|
|
162
|
+
const identifier = getPropertyIgnoreCase(allProps, 'rpmrtid')
|
|
163
|
+
|| getPropertyIgnoreCase(allProps, 'deviceid')
|
|
164
|
+
|| getPropertyIgnoreCase(allProps, 'macaddress')
|
|
165
|
+
|| name;
|
|
151
166
|
devices.push({
|
|
152
167
|
name,
|
|
153
168
|
address,
|
|
154
169
|
port: companion.port,
|
|
155
170
|
airplayPort: airplay?.port || 7000,
|
|
156
|
-
identifier
|
|
171
|
+
identifier,
|
|
157
172
|
model,
|
|
158
173
|
properties: allProps,
|
|
159
174
|
});
|
package/dist/mdns.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdns.js","sourceRoot":"","sources":["../src/mdns.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;
|
|
1
|
+
{"version":3,"file":"mdns.js","sourceRoot":"","sources":["../src/mdns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
// prints the contents of device.json in the format atv-desktop-remote uses, useful for debugging and testing
|
|
5
|
+
|
|
6
|
+
const devicePath = path.join(__dirname, 'device.json');
|
|
7
|
+
const raw = fs.readFileSync(devicePath, 'utf8');
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
|
|
10
|
+
const output = {
|
|
11
|
+
airplay: parsed.credentials.airplay,
|
|
12
|
+
companion: parsed.credentials.companion,
|
|
13
|
+
device: {
|
|
14
|
+
name: parsed.name,
|
|
15
|
+
address: parsed.address,
|
|
16
|
+
port: parsed.port,
|
|
17
|
+
airplayPort: parsed.airplayPort,
|
|
18
|
+
identifier: parsed.identifier,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
console.log(JSON.stringify(output, null, 2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bharper/atv-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Apple TV control library - mDNS discovery, AirPlay/Companion pairing, remote control, keyboard input",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"remote": "ts-node examples/remote.ts"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@bharper/atv-js": "^0.2.3",
|
|
19
18
|
"@noble/ciphers": "^2.1.1",
|
|
20
19
|
"bplist-creator": "^0.1.0",
|
|
21
20
|
"bplist-parser": "^0.3.2",
|
|
@@ -24,7 +23,7 @@
|
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@types/multicast-dns": "^7.2.4",
|
|
27
|
-
"@types/node": "^20.
|
|
26
|
+
"@types/node": "^20.19.33",
|
|
28
27
|
"ts-node": "^10.9.0",
|
|
29
28
|
"typescript": "^5.3.0"
|
|
30
29
|
},
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { HapCredentials, Credentials, parseCredentials, serializeCredentials } f
|
|
|
8
8
|
export { RemoteKey, HidCommand, MediaControlCommand } from './companion/remote';
|
|
9
9
|
export { KeyboardFocusState } from './companion/keyboard';
|
|
10
10
|
|
|
11
|
-
import { AppleTVDevice } from './mdns';
|
|
11
|
+
import { AppleTVDevice, scan as scanDevices } from './mdns';
|
|
12
12
|
import { CompanionConnection } from './companion/connection';
|
|
13
13
|
import { CompanionProtocol } from './companion/protocol';
|
|
14
14
|
import { CompanionPairSetupProcedure } from './companion/auth';
|
|
@@ -118,10 +118,150 @@ export interface AppleTVConnection {
|
|
|
118
118
|
protocol: CompanionProtocol;
|
|
119
119
|
device: AppleTVDevice;
|
|
120
120
|
credentials: Credentials;
|
|
121
|
+
/** Credentials + endpoint used for this connection attempt/result. */
|
|
122
|
+
usedCredentials: ConnectionCredentialsSnapshot;
|
|
123
|
+
/** True when usedCredentials exactly match the values passed to connect(...). */
|
|
124
|
+
usedCredentialsMatchProvided: boolean;
|
|
121
125
|
/** @internal */
|
|
122
126
|
_keyboardFocusState: KeyboardFocusState;
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
export interface ConnectionCredentialsSnapshot {
|
|
130
|
+
airplay: string;
|
|
131
|
+
companion: string;
|
|
132
|
+
device: {
|
|
133
|
+
name: string;
|
|
134
|
+
address: string;
|
|
135
|
+
port: number;
|
|
136
|
+
airplayPort: number;
|
|
137
|
+
identifier: string;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function errorMessage(error: unknown): string {
|
|
142
|
+
if (error instanceof Error) return error.message;
|
|
143
|
+
return String(error);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ID_PROPERTY_KEYS = new Set(['rpmrtid', 'deviceid', 'macaddress']);
|
|
147
|
+
|
|
148
|
+
function normalizeIdentifier(value: string): string {
|
|
149
|
+
return value.trim().toLowerCase();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getIdentifiers(device: AppleTVDevice): Set<string> {
|
|
153
|
+
const identifiers = new Set<string>();
|
|
154
|
+
|
|
155
|
+
if (device.identifier) {
|
|
156
|
+
identifiers.add(normalizeIdentifier(device.identifier));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const [key, value] of Object.entries(device.properties || {})) {
|
|
160
|
+
if (!value || !ID_PROPERTY_KEYS.has(key.toLowerCase())) continue;
|
|
161
|
+
identifiers.add(normalizeIdentifier(value));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return identifiers;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function sharesIdentifier(target: AppleTVDevice, candidate: AppleTVDevice): boolean {
|
|
168
|
+
const targetIds = getIdentifiers(target);
|
|
169
|
+
if (targetIds.size === 0) return false;
|
|
170
|
+
|
|
171
|
+
for (const identifier of getIdentifiers(candidate)) {
|
|
172
|
+
if (targetIds.has(identifier)) return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function deviceMatchScore(target: AppleTVDevice, candidate: AppleTVDevice): number {
|
|
179
|
+
if (sharesIdentifier(target, candidate)) return 100;
|
|
180
|
+
if (candidate.address === target.address) return 10;
|
|
181
|
+
if (candidate.name === target.name) return 1;
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function mergeDiscoveredDevice(target: AppleTVDevice, discovered: AppleTVDevice): AppleTVDevice {
|
|
186
|
+
return {
|
|
187
|
+
...target,
|
|
188
|
+
...discovered,
|
|
189
|
+
properties: { ...target.properties, ...discovered.properties },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function hasCompanionEndpointChanged(current: AppleTVDevice, discovered: AppleTVDevice): boolean {
|
|
194
|
+
return current.address !== discovered.address || current.port !== discovered.port;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toCredentialSnapshot(
|
|
198
|
+
device: AppleTVDevice,
|
|
199
|
+
credentials: Credentials,
|
|
200
|
+
): ConnectionCredentialsSnapshot {
|
|
201
|
+
return {
|
|
202
|
+
airplay: credentials.airplay,
|
|
203
|
+
companion: credentials.companion,
|
|
204
|
+
device: {
|
|
205
|
+
name: device.name,
|
|
206
|
+
address: device.address,
|
|
207
|
+
port: device.port,
|
|
208
|
+
airplayPort: device.airplayPort,
|
|
209
|
+
identifier: device.identifier,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function snapshotsEqual(
|
|
215
|
+
left: ConnectionCredentialsSnapshot,
|
|
216
|
+
right: ConnectionCredentialsSnapshot,
|
|
217
|
+
): boolean {
|
|
218
|
+
return left.airplay === right.airplay
|
|
219
|
+
&& left.companion === right.companion
|
|
220
|
+
&& left.device.name === right.device.name
|
|
221
|
+
&& left.device.address === right.device.address
|
|
222
|
+
&& left.device.port === right.device.port
|
|
223
|
+
&& left.device.airplayPort === right.device.airplayPort
|
|
224
|
+
&& left.device.identifier === right.device.identifier;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function connectCompanion(
|
|
228
|
+
device: AppleTVDevice,
|
|
229
|
+
companionCredentials: string,
|
|
230
|
+
): Promise<CompanionProtocol> {
|
|
231
|
+
const connection = new CompanionConnection(device.address, device.port);
|
|
232
|
+
const protocol = new CompanionProtocol(connection, companionCredentials);
|
|
233
|
+
connection.setListener(protocol);
|
|
234
|
+
await protocol.start();
|
|
235
|
+
return protocol;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function discoverLatestDevice(device: AppleTVDevice): Promise<AppleTVDevice | null> {
|
|
239
|
+
const discovered = await scanDevices(3000, false);
|
|
240
|
+
let bestMatch: AppleTVDevice | null = null;
|
|
241
|
+
let bestScore = 0;
|
|
242
|
+
|
|
243
|
+
for (const candidate of discovered) {
|
|
244
|
+
const score = deviceMatchScore(device, candidate);
|
|
245
|
+
if (score === 0) continue;
|
|
246
|
+
|
|
247
|
+
if (!bestMatch || score > bestScore) {
|
|
248
|
+
bestMatch = candidate;
|
|
249
|
+
bestScore = score;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (score === bestScore) {
|
|
254
|
+
const candidatePortMatches = candidate.port === device.port;
|
|
255
|
+
const currentPortMatches = bestMatch.port === device.port;
|
|
256
|
+
if (candidatePortMatches && !currentPortMatches) {
|
|
257
|
+
bestMatch = candidate;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return bestMatch;
|
|
263
|
+
}
|
|
264
|
+
|
|
125
265
|
/**
|
|
126
266
|
* Connect to an Apple TV using stored credentials.
|
|
127
267
|
* Performs pair-verify and sets up encrypted Companion channel.
|
|
@@ -130,12 +270,40 @@ export async function connect(
|
|
|
130
270
|
device: AppleTVDevice,
|
|
131
271
|
credentials: Credentials,
|
|
132
272
|
): Promise<AppleTVConnection> {
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
273
|
+
let activeDevice = device;
|
|
274
|
+
const providedCredentials = toCredentialSnapshot(device, credentials);
|
|
275
|
+
let protocol: CompanionProtocol;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
279
|
+
} catch (initialError) {
|
|
280
|
+
let discovered: AppleTVDevice | null = null;
|
|
281
|
+
try {
|
|
282
|
+
discovered = await discoverLatestDevice(activeDevice);
|
|
283
|
+
} catch {
|
|
284
|
+
discovered = null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!discovered) {
|
|
288
|
+
throw initialError;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const mergedDevice = mergeDiscoveredDevice(activeDevice, discovered);
|
|
292
|
+
if (!hasCompanionEndpointChanged(activeDevice, mergedDevice)) {
|
|
293
|
+
throw initialError;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
activeDevice = mergedDevice;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
300
|
+
} catch (retryError) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Companion connection failed on saved endpoint ${device.address}:${device.port} and discovered endpoint ${activeDevice.address}:${activeDevice.port}: ${errorMessage(retryError)}`,
|
|
303
|
+
{ cause: initialError instanceof Error ? initialError : undefined },
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
139
307
|
|
|
140
308
|
// Post-connection initialization (order matters!)
|
|
141
309
|
// 1. Send system info
|
|
@@ -177,10 +345,13 @@ export async function connect(
|
|
|
177
345
|
|
|
178
346
|
const conn: AppleTVConnection = {
|
|
179
347
|
protocol,
|
|
180
|
-
device,
|
|
348
|
+
device: activeDevice,
|
|
181
349
|
credentials,
|
|
350
|
+
usedCredentials: toCredentialSnapshot(activeDevice, credentials),
|
|
351
|
+
usedCredentialsMatchProvided: false,
|
|
182
352
|
_keyboardFocusState: KeyboardFocusState.Unknown,
|
|
183
353
|
};
|
|
354
|
+
conn.usedCredentialsMatchProvided = snapshotsEqual(conn.usedCredentials, providedCredentials);
|
|
184
355
|
|
|
185
356
|
// Watch keyboard focus state
|
|
186
357
|
_watchKeyboardFocus(protocol, (state) => {
|
package/src/mdns.ts
CHANGED
|
@@ -42,6 +42,14 @@ function isAppleTvModel(model: string): boolean {
|
|
|
42
42
|
return APPLE_TV_MODELS.has(model);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function getPropertyIgnoreCase(properties: Record<string, string>, key: string): string | undefined {
|
|
46
|
+
const expected = key.toLowerCase();
|
|
47
|
+
for (const [property, value] of Object.entries(properties)) {
|
|
48
|
+
if (property.toLowerCase() === expected) return value;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
export async function scan(timeout = 5000, onlyAppleTV = true): Promise<AppleTVDevice[]> {
|
|
46
54
|
return new Promise((resolve) => {
|
|
47
55
|
const browser = mdns();
|
|
@@ -177,16 +185,24 @@ export async function scan(timeout = 5000, onlyAppleTV = true): Promise<AppleTVD
|
|
|
177
185
|
const allProps = { ...companion.properties, ...airplay?.properties };
|
|
178
186
|
|
|
179
187
|
// Model comes from _device-info._tcp.local service, not companion/airplay
|
|
180
|
-
const model = deviceInfoModels.get(name)
|
|
188
|
+
const model = deviceInfoModels.get(name)
|
|
189
|
+
|| getPropertyIgnoreCase(allProps, 'model')
|
|
190
|
+
|| getPropertyIgnoreCase(allProps, 'rpmd')
|
|
191
|
+
|| '';
|
|
181
192
|
|
|
182
193
|
if (onlyAppleTV && !isAppleTvModel(model)) continue;
|
|
183
194
|
|
|
195
|
+
const identifier = getPropertyIgnoreCase(allProps, 'rpmrtid')
|
|
196
|
+
|| getPropertyIgnoreCase(allProps, 'deviceid')
|
|
197
|
+
|| getPropertyIgnoreCase(allProps, 'macaddress')
|
|
198
|
+
|| name;
|
|
199
|
+
|
|
184
200
|
devices.push({
|
|
185
201
|
name,
|
|
186
202
|
address,
|
|
187
203
|
port: companion.port,
|
|
188
204
|
airplayPort: airplay?.port || 7000,
|
|
189
|
-
identifier
|
|
205
|
+
identifier,
|
|
190
206
|
model,
|
|
191
207
|
properties: allProps,
|
|
192
208
|
});
|