@basmilius/apple-companion-link 0.0.80 → 0.0.82
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.js +567 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1 +1,567 @@
|
|
|
1
|
-
|
|
1
|
+
// src/const.ts
|
|
2
|
+
var HidCommand = {
|
|
3
|
+
Up: 1,
|
|
4
|
+
Down: 2,
|
|
5
|
+
Left: 3,
|
|
6
|
+
Right: 4,
|
|
7
|
+
Menu: 5,
|
|
8
|
+
Select: 6,
|
|
9
|
+
Home: 7,
|
|
10
|
+
VolumeUp: 8,
|
|
11
|
+
VolumeDown: 9,
|
|
12
|
+
Siri: 10,
|
|
13
|
+
Screensaver: 11,
|
|
14
|
+
Sleep: 12,
|
|
15
|
+
Wake: 13,
|
|
16
|
+
PlayPause: 14,
|
|
17
|
+
ChannelIncrement: 15,
|
|
18
|
+
ChannelDecrement: 16,
|
|
19
|
+
Guide: 17,
|
|
20
|
+
PageUp: 18,
|
|
21
|
+
PageDown: 19
|
|
22
|
+
};
|
|
23
|
+
var MediaControlCommand = {
|
|
24
|
+
Play: 1,
|
|
25
|
+
Pause: 2,
|
|
26
|
+
NextTrack: 3,
|
|
27
|
+
PreviousTrack: 4,
|
|
28
|
+
GetVolume: 5,
|
|
29
|
+
SetVolume: 6,
|
|
30
|
+
SkipBy: 7,
|
|
31
|
+
FastForwardBegin: 8,
|
|
32
|
+
FastForwardEnd: 9,
|
|
33
|
+
RewindBegin: 10,
|
|
34
|
+
RewindEnd: 11,
|
|
35
|
+
GetCaptionSettings: 12,
|
|
36
|
+
SetCaptionSettings: 13
|
|
37
|
+
};
|
|
38
|
+
// src/utils.ts
|
|
39
|
+
function convertAttentionState(state) {
|
|
40
|
+
switch (state) {
|
|
41
|
+
case 1:
|
|
42
|
+
return "asleep";
|
|
43
|
+
case 2:
|
|
44
|
+
return "screensaver";
|
|
45
|
+
case 3:
|
|
46
|
+
return "awake";
|
|
47
|
+
case 4:
|
|
48
|
+
return "idle";
|
|
49
|
+
default:
|
|
50
|
+
return "unknown";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// src/api.ts
|
|
54
|
+
import { randomInt } from "node:crypto";
|
|
55
|
+
import { reporter, waitFor } from "@basmilius/apple-common";
|
|
56
|
+
import { OPack, Plist } from "@basmilius/apple-encoding";
|
|
57
|
+
|
|
58
|
+
// src/messages.ts
|
|
59
|
+
var FrameType = {
|
|
60
|
+
Unknown: 0,
|
|
61
|
+
Noop: 1,
|
|
62
|
+
PS_Start: 3,
|
|
63
|
+
PS_Next: 4,
|
|
64
|
+
PV_Start: 5,
|
|
65
|
+
PV_Next: 6,
|
|
66
|
+
U_OPACK: 7,
|
|
67
|
+
E_OPACK: 8,
|
|
68
|
+
P_OPACK: 9,
|
|
69
|
+
PA_Request: 10,
|
|
70
|
+
PA_Response: 11,
|
|
71
|
+
SessionStartRequest: 16,
|
|
72
|
+
SessionStartResponse: 17,
|
|
73
|
+
SessionData: 18,
|
|
74
|
+
FamilyIdentityRequest: 32,
|
|
75
|
+
FamilyIdentityResponse: 33,
|
|
76
|
+
FamilyIdentityUpdate: 34
|
|
77
|
+
};
|
|
78
|
+
var MessageType = {
|
|
79
|
+
Event: 1,
|
|
80
|
+
Request: 2,
|
|
81
|
+
Response: 3
|
|
82
|
+
};
|
|
83
|
+
var OPackFrameTypes = [
|
|
84
|
+
FrameType.PS_Start,
|
|
85
|
+
FrameType.PS_Next,
|
|
86
|
+
FrameType.PV_Start,
|
|
87
|
+
FrameType.PV_Next,
|
|
88
|
+
FrameType.U_OPACK,
|
|
89
|
+
FrameType.E_OPACK,
|
|
90
|
+
FrameType.P_OPACK
|
|
91
|
+
];
|
|
92
|
+
var PairFrameTypes = [
|
|
93
|
+
FrameType.PS_Start,
|
|
94
|
+
FrameType.PS_Next,
|
|
95
|
+
FrameType.PV_Start,
|
|
96
|
+
FrameType.PV_Next
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// src/api.ts
|
|
100
|
+
class CompanionLinkApi {
|
|
101
|
+
get socket() {
|
|
102
|
+
return this.#protocol.socket;
|
|
103
|
+
}
|
|
104
|
+
#protocol;
|
|
105
|
+
constructor(protocol) {
|
|
106
|
+
this.#protocol = protocol;
|
|
107
|
+
}
|
|
108
|
+
async fetchMediaControlStatus() {
|
|
109
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
110
|
+
_i: "FetchMediaControlStatus",
|
|
111
|
+
_t: MessageType.Request,
|
|
112
|
+
_c: {}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async fetchNowPlayingInfo() {
|
|
116
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
117
|
+
_i: "FetchCurrentNowPlayingInfoEvent",
|
|
118
|
+
_t: MessageType.Request,
|
|
119
|
+
_c: {}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async fetchSupportedActions() {
|
|
123
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
124
|
+
_i: "FetchSupportedActionsEvent",
|
|
125
|
+
_t: MessageType.Request,
|
|
126
|
+
_c: {}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async getAttentionState() {
|
|
130
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
131
|
+
_i: "FetchAttentionState",
|
|
132
|
+
_t: MessageType.Request,
|
|
133
|
+
_c: {}
|
|
134
|
+
});
|
|
135
|
+
const { _c } = objectOrFail(payload);
|
|
136
|
+
return convertAttentionState(_c.state);
|
|
137
|
+
}
|
|
138
|
+
async getLaunchableApps() {
|
|
139
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
140
|
+
_i: "FetchLaunchableApplicationsEvent",
|
|
141
|
+
_t: MessageType.Request,
|
|
142
|
+
_c: {}
|
|
143
|
+
});
|
|
144
|
+
const { _c } = objectOrFail(payload);
|
|
145
|
+
return Object.entries(_c).map(([bundleId, name]) => ({
|
|
146
|
+
bundleId,
|
|
147
|
+
name
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
async getSiriRemoteInfo() {
|
|
151
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
152
|
+
_i: "FetchSiriRemoteInfo",
|
|
153
|
+
_t: MessageType.Request,
|
|
154
|
+
_c: {}
|
|
155
|
+
});
|
|
156
|
+
return Plist.parse(Buffer.from(payload["_c"]["SiriRemoteInfoKey"]).buffer);
|
|
157
|
+
}
|
|
158
|
+
async getUserAccounts() {
|
|
159
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
160
|
+
_i: "FetchUserAccountsEvent",
|
|
161
|
+
_t: MessageType.Request,
|
|
162
|
+
_c: {}
|
|
163
|
+
});
|
|
164
|
+
const { _c } = objectOrFail(payload);
|
|
165
|
+
return Object.entries(_c).map(([accountId, name]) => ({
|
|
166
|
+
accountId,
|
|
167
|
+
name
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
async hidCommand(command, down = false) {
|
|
171
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
172
|
+
_i: "_hidC",
|
|
173
|
+
_t: MessageType.Request,
|
|
174
|
+
_c: {
|
|
175
|
+
_hBtS: down ? 1 : 2,
|
|
176
|
+
_hidC: HidCommand[command]
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async launchApp(bundleId) {
|
|
181
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
182
|
+
_i: "_launchApp",
|
|
183
|
+
_t: MessageType.Request,
|
|
184
|
+
_c: {
|
|
185
|
+
_bundleID: bundleId
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async launchUrl(url) {
|
|
190
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
191
|
+
_i: "_launchApp",
|
|
192
|
+
_t: MessageType.Request,
|
|
193
|
+
_c: {
|
|
194
|
+
_urlS: url
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async mediaControlCommand(command, content) {
|
|
199
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
200
|
+
_i: "_mcc",
|
|
201
|
+
_t: MessageType.Request,
|
|
202
|
+
_c: {
|
|
203
|
+
_mcc: MediaControlCommand[command],
|
|
204
|
+
...content || {}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return objectOrFail(payload);
|
|
208
|
+
}
|
|
209
|
+
async pressButton(command, type = "SingleTap", holdDelayMs = 500) {
|
|
210
|
+
switch (type) {
|
|
211
|
+
case "DoubleTap":
|
|
212
|
+
await this.hidCommand(command, true);
|
|
213
|
+
await this.hidCommand(command, false);
|
|
214
|
+
await this.hidCommand(command, true);
|
|
215
|
+
await this.hidCommand(command, false);
|
|
216
|
+
break;
|
|
217
|
+
case "Hold":
|
|
218
|
+
await this.hidCommand(command, true);
|
|
219
|
+
await waitFor(holdDelayMs);
|
|
220
|
+
await this.hidCommand(command, false);
|
|
221
|
+
break;
|
|
222
|
+
case "SingleTap":
|
|
223
|
+
await this.hidCommand(command, true);
|
|
224
|
+
await this.hidCommand(command, false);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async switchUserAccount(accountId) {
|
|
229
|
+
await this.socket.exchange(FrameType.E_OPACK, {
|
|
230
|
+
_i: "SwitchUserAccountEvent",
|
|
231
|
+
_t: MessageType.Request,
|
|
232
|
+
_c: {
|
|
233
|
+
SwitchAccountID: accountId
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async _subscribe(event, fn) {
|
|
238
|
+
this.socket.on(event, fn);
|
|
239
|
+
await this.socket.send(FrameType.E_OPACK, {
|
|
240
|
+
_i: "_interest",
|
|
241
|
+
_t: MessageType.Event,
|
|
242
|
+
_c: {
|
|
243
|
+
_regEvents: [event]
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async _unsubscribe(event, fn) {
|
|
248
|
+
if (fn) {
|
|
249
|
+
this.socket.off(event, fn);
|
|
250
|
+
}
|
|
251
|
+
await this.socket.send(FrameType.E_OPACK, {
|
|
252
|
+
_i: "_interest",
|
|
253
|
+
_t: MessageType.Event,
|
|
254
|
+
_c: {
|
|
255
|
+
_deregEvents: [event]
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async _sessionStart() {
|
|
260
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
261
|
+
_i: "_sessionStart",
|
|
262
|
+
_t: MessageType.Request,
|
|
263
|
+
_c: {
|
|
264
|
+
_srvT: "com.apple.tvremoteservices",
|
|
265
|
+
_sid: randomInt(0, 2 ** 32 - 1)
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
return objectOrFail(payload);
|
|
269
|
+
}
|
|
270
|
+
async _systemInfo(pairingId) {
|
|
271
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
272
|
+
_i: "_systemInfo",
|
|
273
|
+
_t: MessageType.Request,
|
|
274
|
+
_c: {
|
|
275
|
+
_bf: 0,
|
|
276
|
+
_cf: 512,
|
|
277
|
+
_clFl: 128,
|
|
278
|
+
_i: "cafecafecafe",
|
|
279
|
+
_idsID: pairingId.toString(),
|
|
280
|
+
_pubID: "FF:70:79:61:74:76",
|
|
281
|
+
_sf: 256,
|
|
282
|
+
_sv: "170.18",
|
|
283
|
+
model: "iPhone10,6",
|
|
284
|
+
name: "Bas Companion Link"
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return objectOrFail(payload);
|
|
288
|
+
}
|
|
289
|
+
async _touchStart() {
|
|
290
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
291
|
+
_i: "_touchStart",
|
|
292
|
+
_t: MessageType.Request,
|
|
293
|
+
_c: {
|
|
294
|
+
_height: OPack.float(1000),
|
|
295
|
+
_tFl: 0,
|
|
296
|
+
_width: OPack.float(1000)
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
return objectOrFail(payload);
|
|
300
|
+
}
|
|
301
|
+
async _tvrcSessionStart() {
|
|
302
|
+
const [, payload] = await this.socket.exchange(FrameType.E_OPACK, {
|
|
303
|
+
_i: "TVRCSessionStart",
|
|
304
|
+
_t: MessageType.Request,
|
|
305
|
+
_btHP: false,
|
|
306
|
+
_inUseProc: "tvremoted",
|
|
307
|
+
_c: {}
|
|
308
|
+
});
|
|
309
|
+
return objectOrFail(payload);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function objectOrFail(obj) {
|
|
313
|
+
if (typeof obj === "object") {
|
|
314
|
+
return obj;
|
|
315
|
+
}
|
|
316
|
+
reporter.error("Expected an object.", { obj });
|
|
317
|
+
throw new Error("Expected an object.");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/pairing.ts
|
|
321
|
+
import { AccessoryPair } from "@basmilius/apple-common";
|
|
322
|
+
class CompanionLinkPairing {
|
|
323
|
+
get internal() {
|
|
324
|
+
return this.#internal;
|
|
325
|
+
}
|
|
326
|
+
get socket() {
|
|
327
|
+
return this.#protocol.socket;
|
|
328
|
+
}
|
|
329
|
+
#internal;
|
|
330
|
+
#protocol;
|
|
331
|
+
constructor(protocol) {
|
|
332
|
+
this.#internal = new AccessoryPair(this.#request.bind(this));
|
|
333
|
+
this.#protocol = protocol;
|
|
334
|
+
}
|
|
335
|
+
async start() {
|
|
336
|
+
await this.#internal.start();
|
|
337
|
+
}
|
|
338
|
+
async pin(askPin) {
|
|
339
|
+
return this.#internal.pin(askPin);
|
|
340
|
+
}
|
|
341
|
+
async transient() {
|
|
342
|
+
return this.#internal.transient();
|
|
343
|
+
}
|
|
344
|
+
async#request(step, data) {
|
|
345
|
+
const frameType = step === "m1" ? FrameType.PS_Start : FrameType.PS_Next;
|
|
346
|
+
const [, response] = await this.socket.exchange(frameType, {
|
|
347
|
+
_pd: data,
|
|
348
|
+
_pwTy: 1
|
|
349
|
+
});
|
|
350
|
+
if (typeof response !== "object" || response === null) {
|
|
351
|
+
throw new Error("Invalid response from receiver.");
|
|
352
|
+
}
|
|
353
|
+
return response["_pd"];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/socket.ts
|
|
358
|
+
import { randomInt as randomInt2 } from "node:crypto";
|
|
359
|
+
import { Chacha20, ENCRYPTION, EncryptionAwareConnection, reporter as reporter2 } from "@basmilius/apple-common";
|
|
360
|
+
import { OPack as OPack2 } from "@basmilius/apple-encoding";
|
|
361
|
+
var HEADER_BYTES = 4;
|
|
362
|
+
|
|
363
|
+
class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
364
|
+
get #encryption() {
|
|
365
|
+
return this[ENCRYPTION];
|
|
366
|
+
}
|
|
367
|
+
#queue = {};
|
|
368
|
+
#buffer = Buffer.alloc(0);
|
|
369
|
+
#xid;
|
|
370
|
+
constructor(address, port) {
|
|
371
|
+
super(address, port);
|
|
372
|
+
this.#xid = randomInt2(0, 2 ** 16);
|
|
373
|
+
this.onData = this.onData.bind(this);
|
|
374
|
+
this.on("data", this.onData);
|
|
375
|
+
}
|
|
376
|
+
async exchange(type, obj) {
|
|
377
|
+
const _x = this.#xid;
|
|
378
|
+
return new Promise((resolve, reject) => {
|
|
379
|
+
if (PairFrameTypes.includes(type)) {
|
|
380
|
+
this.#queue[-1] = resolve;
|
|
381
|
+
} else {
|
|
382
|
+
this.#queue[_x] = resolve;
|
|
383
|
+
}
|
|
384
|
+
this.send(type, obj).catch(reject);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
async send(type, obj) {
|
|
388
|
+
const _x = this.#xid++;
|
|
389
|
+
obj._x ??= OPack2.sizedInt(_x, 8);
|
|
390
|
+
let payload = Buffer.from(OPack2.encode(obj));
|
|
391
|
+
let payloadLength = payload.byteLength;
|
|
392
|
+
if (this.isEncrypted && payloadLength > 0) {
|
|
393
|
+
payloadLength += Chacha20.CHACHA20_AUTH_TAG_LENGTH;
|
|
394
|
+
}
|
|
395
|
+
const header = Buffer.allocUnsafe(4);
|
|
396
|
+
header.writeUint8(type, 0);
|
|
397
|
+
header.writeUintBE(payloadLength, 1, 3);
|
|
398
|
+
let data;
|
|
399
|
+
if (this.isEncrypted) {
|
|
400
|
+
const nonce = Buffer.alloc(12);
|
|
401
|
+
nonce.writeBigUInt64LE(BigInt(this.#encryption.writeCount++), 0);
|
|
402
|
+
const encrypted = Chacha20.encrypt(this.#encryption.writeKey, nonce, header, payload);
|
|
403
|
+
data = Buffer.concat([header, encrypted.ciphertext, encrypted.authTag]);
|
|
404
|
+
} else {
|
|
405
|
+
data = Buffer.concat([header, payload]);
|
|
406
|
+
}
|
|
407
|
+
reporter2.raw("Sending data frame...", this.isEncrypted, Buffer.from(data).toString("hex"), obj);
|
|
408
|
+
return await this.write(data);
|
|
409
|
+
}
|
|
410
|
+
async onData(buffer) {
|
|
411
|
+
reporter2.raw("Received data frame", buffer.toString("hex"));
|
|
412
|
+
this.#buffer = Buffer.concat([this.#buffer, buffer]);
|
|
413
|
+
while (this.#buffer.byteLength >= HEADER_BYTES) {
|
|
414
|
+
const header = this.#buffer.subarray(0, HEADER_BYTES);
|
|
415
|
+
const payloadLength = header.readUintBE(1, 3);
|
|
416
|
+
const totalLength = HEADER_BYTES + payloadLength;
|
|
417
|
+
if (this.#buffer.byteLength < totalLength) {
|
|
418
|
+
reporter2.warn(`Not enough data yet, waiting on the next frame.. needed=${totalLength} available=${this.#buffer.byteLength} receivedLength=${buffer.byteLength}`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
reporter2.raw(`Frame found length=${totalLength} availableLength=${this.#buffer.byteLength} receivedLength=${buffer.byteLength}`);
|
|
422
|
+
const frame = Buffer.from(this.#buffer.subarray(0, totalLength));
|
|
423
|
+
this.#buffer = this.#buffer.subarray(totalLength);
|
|
424
|
+
reporter2.raw(`Handle frame, ${this.#buffer.byteLength} bytes left...`);
|
|
425
|
+
const data = await this.#decrypt(frame);
|
|
426
|
+
let payload = data.subarray(4, totalLength);
|
|
427
|
+
await this.#handle(header, payload);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async#decrypt(data) {
|
|
431
|
+
if (!this.isEncrypted) {
|
|
432
|
+
return data;
|
|
433
|
+
}
|
|
434
|
+
const header = data.subarray(0, 4);
|
|
435
|
+
const payloadLength = header.readUintBE(1, 3);
|
|
436
|
+
const payload = data.subarray(4, 4 + payloadLength);
|
|
437
|
+
const authTag = payload.subarray(payload.byteLength - 16);
|
|
438
|
+
const ciphertext = payload.subarray(0, payload.byteLength - 16);
|
|
439
|
+
const nonce = Buffer.alloc(12);
|
|
440
|
+
nonce.writeBigUint64LE(BigInt(this.#encryption.readCount++), 0);
|
|
441
|
+
const decrypted = Chacha20.decrypt(this.#encryption.readKey, nonce, header, ciphertext, authTag);
|
|
442
|
+
return Buffer.concat([header, decrypted, authTag]);
|
|
443
|
+
}
|
|
444
|
+
async#handle(header, payload) {
|
|
445
|
+
const type = header.readInt8();
|
|
446
|
+
if (!OPackFrameTypes.includes(type)) {
|
|
447
|
+
reporter2.warn("Packet not handled, no opack frame.");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
payload = OPack2.decode(payload);
|
|
451
|
+
reporter2.raw("Decoded OPACK", { header, payload });
|
|
452
|
+
if ("_x" in payload) {
|
|
453
|
+
const _x = payload._x;
|
|
454
|
+
if (_x in this.#queue) {
|
|
455
|
+
const resolve = this.#queue[_x] ?? null;
|
|
456
|
+
resolve?.([header, payload]);
|
|
457
|
+
delete this.#queue[_x];
|
|
458
|
+
} else if ("_i" in payload) {
|
|
459
|
+
this.emit(payload["_i"], payload["_c"]);
|
|
460
|
+
} else {
|
|
461
|
+
const content = payload["_c"];
|
|
462
|
+
const keys = Object.keys(content).map((k) => k.substring(0, -3));
|
|
463
|
+
for (const key of keys) {
|
|
464
|
+
this.emit(key, content[key]);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else if (this.#queue[-1]) {
|
|
468
|
+
const _x = -1;
|
|
469
|
+
const resolve = this.#queue[_x] ?? null;
|
|
470
|
+
resolve?.([header, payload]);
|
|
471
|
+
delete this.#queue[_x];
|
|
472
|
+
} else {
|
|
473
|
+
reporter2.warn("No handler for message", [header, payload]);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/verify.ts
|
|
479
|
+
import { AccessoryVerify, hkdf } from "@basmilius/apple-common";
|
|
480
|
+
class CompanionLinkVerify {
|
|
481
|
+
get socket() {
|
|
482
|
+
return this.#protocol.socket;
|
|
483
|
+
}
|
|
484
|
+
#internal;
|
|
485
|
+
#protocol;
|
|
486
|
+
constructor(protocol) {
|
|
487
|
+
this.#internal = new AccessoryVerify(this.#request.bind(this));
|
|
488
|
+
this.#protocol = protocol;
|
|
489
|
+
}
|
|
490
|
+
async start(credentials) {
|
|
491
|
+
const keys = await this.#internal.start(credentials);
|
|
492
|
+
const accessoryToControllerKey = hkdf({
|
|
493
|
+
hash: "sha512",
|
|
494
|
+
key: keys.sharedSecret,
|
|
495
|
+
length: 32,
|
|
496
|
+
salt: Buffer.alloc(0),
|
|
497
|
+
info: Buffer.from("ServerEncrypt-main")
|
|
498
|
+
});
|
|
499
|
+
const controllerToAccessoryKey = hkdf({
|
|
500
|
+
hash: "sha512",
|
|
501
|
+
key: keys.sharedSecret,
|
|
502
|
+
length: 32,
|
|
503
|
+
salt: Buffer.alloc(0),
|
|
504
|
+
info: Buffer.from("ClientEncrypt-main")
|
|
505
|
+
});
|
|
506
|
+
return {
|
|
507
|
+
accessoryToControllerKey,
|
|
508
|
+
controllerToAccessoryKey,
|
|
509
|
+
pairingId: keys.pairingId,
|
|
510
|
+
sharedSecret: keys.sharedSecret
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
async#request(step, data) {
|
|
514
|
+
const frameType = step === "m1" ? FrameType.PV_Start : FrameType.PV_Next;
|
|
515
|
+
const [, response] = await this.socket.exchange(frameType, {
|
|
516
|
+
_pd: data,
|
|
517
|
+
_auTy: 4
|
|
518
|
+
});
|
|
519
|
+
if (typeof response !== "object" || response === null) {
|
|
520
|
+
throw new Error("Invalid response from receiver.");
|
|
521
|
+
}
|
|
522
|
+
return response["_pd"];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/protocol.ts
|
|
527
|
+
class CompanionLink {
|
|
528
|
+
get api() {
|
|
529
|
+
return this.#api;
|
|
530
|
+
}
|
|
531
|
+
get device() {
|
|
532
|
+
return this.#device;
|
|
533
|
+
}
|
|
534
|
+
get socket() {
|
|
535
|
+
return this.#socket;
|
|
536
|
+
}
|
|
537
|
+
get pairing() {
|
|
538
|
+
return this.#pairing;
|
|
539
|
+
}
|
|
540
|
+
get verify() {
|
|
541
|
+
return this.#verify;
|
|
542
|
+
}
|
|
543
|
+
#api;
|
|
544
|
+
#device;
|
|
545
|
+
#socket;
|
|
546
|
+
#pairing;
|
|
547
|
+
#verify;
|
|
548
|
+
constructor(device) {
|
|
549
|
+
this.#device = device;
|
|
550
|
+
this.#socket = new CompanionLinkSocket(device.address, device.service.port);
|
|
551
|
+
this.#api = new CompanionLinkApi(this);
|
|
552
|
+
this.#pairing = new CompanionLinkPairing(this);
|
|
553
|
+
this.#verify = new CompanionLinkVerify(this);
|
|
554
|
+
}
|
|
555
|
+
async connect() {
|
|
556
|
+
await this.#socket.connect();
|
|
557
|
+
}
|
|
558
|
+
async disconnect() {
|
|
559
|
+
await this.#socket.disconnect();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
export {
|
|
563
|
+
convertAttentionState,
|
|
564
|
+
MediaControlCommand,
|
|
565
|
+
HidCommand,
|
|
566
|
+
CompanionLink
|
|
567
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basmilius/apple-companion-link",
|
|
3
3
|
"description": "Implementation of Apple's Companion Link in Node.js.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.82",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@basmilius/apple-common": "0.0.
|
|
44
|
-
"@basmilius/apple-encoding": "0.0.
|
|
43
|
+
"@basmilius/apple-common": "0.0.82",
|
|
44
|
+
"@basmilius/apple-encoding": "0.0.82"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@basmilius/tools": "^2.23.0",
|