@bharper/atv-js 0.2.6 → 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 +89 -9
- 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 +122 -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,EAAuB,MAAM,QAAQ,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAM/D,OAAO,EAAE,cAAc,EAAE,WAAW,EAA0C,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EACL,qBAAqB,EAGtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAgB,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAKL,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAM9B,MAAM,WAAW,cAAc;IAC7B,gBAAgB;IAChB,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC;IAC/B,gBAAgB;IAChB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,gBAAgB;IAChB,kBAAkB,CAAC,EAAE,iBAAiB,CAAC;IACvC,gBAAgB;IAChB,mBAAmB,CAAC,EAAE,2BAA2B,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAGxF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CAKzB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAe1F;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CAYzB;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,WAAW,CAAC;IACzB,gBAAgB;IAChB,mBAAmB,EAAE,kBAAkB,CAAC;CACzC;
|
|
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,EAAuB,MAAM,QAAQ,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAM/D,OAAO,EAAE,cAAc,EAAE,WAAW,EAA0C,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EACL,qBAAqB,EAGtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAgB,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAKL,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAM9B,MAAM,WAAW,cAAc;IAC7B,gBAAgB;IAChB,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC;IAC/B,gBAAgB;IAChB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,gBAAgB;IAChB,kBAAkB,CAAC,EAAE,iBAAiB,CAAC;IACvC,gBAAgB;IAChB,mBAAmB,CAAC,EAAE,2BAA2B,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAGxF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CAKzB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAe1F;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CAYzB;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,WAAW,CAAC;IACzB,sEAAsE;IACtE,eAAe,EAAE,6BAA6B,CAAC;IAC/C,iFAAiF;IACjF,4BAA4B,EAAE,OAAO,CAAC;IACtC,gBAAgB;IAChB,mBAAmB,EAAE,kBAAkB,CAAC;CACzC;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AA8HD;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,WAAW,GACvB,OAAO,CAAC,iBAAiB,CAAC,CA0F5B;AAID;;GAEG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7F;AAED,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AAIlC;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAErF;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAE7E;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAID;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAEhG;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAExD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAE5D"}
|
package/dist/index.js
CHANGED
|
@@ -94,12 +94,40 @@ function errorMessage(error) {
|
|
|
94
94
|
return error.message;
|
|
95
95
|
return String(error);
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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;
|
|
100
126
|
if (candidate.address === target.address)
|
|
101
|
-
return
|
|
102
|
-
|
|
127
|
+
return 10;
|
|
128
|
+
if (candidate.name === target.name)
|
|
129
|
+
return 1;
|
|
130
|
+
return 0;
|
|
103
131
|
}
|
|
104
132
|
function mergeDiscoveredDevice(target, discovered) {
|
|
105
133
|
return {
|
|
@@ -108,6 +136,31 @@ function mergeDiscoveredDevice(target, discovered) {
|
|
|
108
136
|
properties: { ...target.properties, ...discovered.properties },
|
|
109
137
|
};
|
|
110
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
|
+
}
|
|
111
164
|
async function connectCompanion(device, companionCredentials) {
|
|
112
165
|
const connection = new connection_1.CompanionConnection(device.address, device.port);
|
|
113
166
|
const protocol = new protocol_1.CompanionProtocol(connection, companionCredentials);
|
|
@@ -117,7 +170,26 @@ async function connectCompanion(device, companionCredentials) {
|
|
|
117
170
|
}
|
|
118
171
|
async function discoverLatestDevice(device) {
|
|
119
172
|
const discovered = await (0, mdns_2.scan)(3000, false);
|
|
120
|
-
|
|
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;
|
|
121
193
|
}
|
|
122
194
|
/**
|
|
123
195
|
* Connect to an Apple TV using stored credentials.
|
|
@@ -125,6 +197,7 @@ async function discoverLatestDevice(device) {
|
|
|
125
197
|
*/
|
|
126
198
|
async function connect(device, credentials) {
|
|
127
199
|
let activeDevice = device;
|
|
200
|
+
const providedCredentials = toCredentialSnapshot(device, credentials);
|
|
128
201
|
let protocol;
|
|
129
202
|
try {
|
|
130
203
|
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
@@ -137,15 +210,19 @@ async function connect(device, credentials) {
|
|
|
137
210
|
catch {
|
|
138
211
|
discovered = null;
|
|
139
212
|
}
|
|
140
|
-
if (!discovered
|
|
213
|
+
if (!discovered) {
|
|
214
|
+
throw initialError;
|
|
215
|
+
}
|
|
216
|
+
const mergedDevice = mergeDiscoveredDevice(activeDevice, discovered);
|
|
217
|
+
if (!hasCompanionEndpointChanged(activeDevice, mergedDevice)) {
|
|
141
218
|
throw initialError;
|
|
142
219
|
}
|
|
143
|
-
activeDevice =
|
|
220
|
+
activeDevice = mergedDevice;
|
|
144
221
|
try {
|
|
145
222
|
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
146
223
|
}
|
|
147
224
|
catch (retryError) {
|
|
148
|
-
throw new Error(`Companion connection failed on saved
|
|
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 });
|
|
149
226
|
}
|
|
150
227
|
}
|
|
151
228
|
// Post-connection initialization (order matters!)
|
|
@@ -185,8 +262,11 @@ async function connect(device, credentials) {
|
|
|
185
262
|
protocol,
|
|
186
263
|
device: activeDevice,
|
|
187
264
|
credentials,
|
|
265
|
+
usedCredentials: toCredentialSnapshot(activeDevice, credentials),
|
|
266
|
+
usedCredentialsMatchProvided: false,
|
|
188
267
|
_keyboardFocusState: keyboard_2.KeyboardFocusState.Unknown,
|
|
189
268
|
};
|
|
269
|
+
conn.usedCredentialsMatchProvided = snapshotsEqual(conn.usedCredentials, providedCredentials);
|
|
190
270
|
// Watch keyboard focus state
|
|
191
271
|
(0, keyboard_2.watchKeyboardFocus)(protocol, (state) => {
|
|
192
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
|
@@ -118,19 +118,68 @@ 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
|
+
|
|
125
141
|
function errorMessage(error: unknown): string {
|
|
126
142
|
if (error instanceof Error) return error.message;
|
|
127
143
|
return String(error);
|
|
128
144
|
}
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
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;
|
|
134
183
|
}
|
|
135
184
|
|
|
136
185
|
function mergeDiscoveredDevice(target: AppleTVDevice, discovered: AppleTVDevice): AppleTVDevice {
|
|
@@ -141,6 +190,40 @@ function mergeDiscoveredDevice(target: AppleTVDevice, discovered: AppleTVDevice)
|
|
|
141
190
|
};
|
|
142
191
|
}
|
|
143
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
|
+
|
|
144
227
|
async function connectCompanion(
|
|
145
228
|
device: AppleTVDevice,
|
|
146
229
|
companionCredentials: string,
|
|
@@ -154,7 +237,29 @@ async function connectCompanion(
|
|
|
154
237
|
|
|
155
238
|
async function discoverLatestDevice(device: AppleTVDevice): Promise<AppleTVDevice | null> {
|
|
156
239
|
const discovered = await scanDevices(3000, false);
|
|
157
|
-
|
|
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;
|
|
158
263
|
}
|
|
159
264
|
|
|
160
265
|
/**
|
|
@@ -166,6 +271,7 @@ export async function connect(
|
|
|
166
271
|
credentials: Credentials,
|
|
167
272
|
): Promise<AppleTVConnection> {
|
|
168
273
|
let activeDevice = device;
|
|
274
|
+
const providedCredentials = toCredentialSnapshot(device, credentials);
|
|
169
275
|
let protocol: CompanionProtocol;
|
|
170
276
|
|
|
171
277
|
try {
|
|
@@ -178,17 +284,22 @@ export async function connect(
|
|
|
178
284
|
discovered = null;
|
|
179
285
|
}
|
|
180
286
|
|
|
181
|
-
if (!discovered
|
|
287
|
+
if (!discovered) {
|
|
288
|
+
throw initialError;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const mergedDevice = mergeDiscoveredDevice(activeDevice, discovered);
|
|
292
|
+
if (!hasCompanionEndpointChanged(activeDevice, mergedDevice)) {
|
|
182
293
|
throw initialError;
|
|
183
294
|
}
|
|
184
295
|
|
|
185
|
-
activeDevice =
|
|
296
|
+
activeDevice = mergedDevice;
|
|
186
297
|
|
|
187
298
|
try {
|
|
188
299
|
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
189
300
|
} catch (retryError) {
|
|
190
301
|
throw new Error(
|
|
191
|
-
`Companion connection failed on saved
|
|
302
|
+
`Companion connection failed on saved endpoint ${device.address}:${device.port} and discovered endpoint ${activeDevice.address}:${activeDevice.port}: ${errorMessage(retryError)}`,
|
|
192
303
|
{ cause: initialError instanceof Error ? initialError : undefined },
|
|
193
304
|
);
|
|
194
305
|
}
|
|
@@ -236,8 +347,11 @@ export async function connect(
|
|
|
236
347
|
protocol,
|
|
237
348
|
device: activeDevice,
|
|
238
349
|
credentials,
|
|
350
|
+
usedCredentials: toCredentialSnapshot(activeDevice, credentials),
|
|
351
|
+
usedCredentialsMatchProvided: false,
|
|
239
352
|
_keyboardFocusState: KeyboardFocusState.Unknown,
|
|
240
353
|
};
|
|
354
|
+
conn.usedCredentialsMatchProvided = snapshotsEqual(conn.usedCredentials, providedCredentials);
|
|
241
355
|
|
|
242
356
|
// Watch keyboard focus state
|
|
243
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
|
});
|