@basmilius/apple-companion-link 0.0.95 → 0.0.96
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 +0 -1
- package/dist/index.js +233 -255
- package/dist/pairing.d.ts +0 -2
- package/dist/protocol.d.ts +22 -3
- package/dist/verify.d.ts +0 -2
- package/package.json +3 -3
- package/dist/api.d.ts +0 -28
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -50,10 +50,10 @@ function convertAttentionState(state) {
|
|
|
50
50
|
return "unknown";
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
// src/
|
|
54
|
-
import { randomInt } from "node:crypto";
|
|
55
|
-
import { reporter, waitFor } from "@basmilius/apple-common";
|
|
56
|
-
import { OPack, Plist } from "@basmilius/apple-encoding";
|
|
53
|
+
// src/protocol.ts
|
|
54
|
+
import { randomInt as randomInt2 } from "node:crypto";
|
|
55
|
+
import { reporter as reporter2, waitFor } from "@basmilius/apple-common";
|
|
56
|
+
import { OPack as OPack2, Plist } from "@basmilius/apple-encoding";
|
|
57
57
|
|
|
58
58
|
// src/messages.ts
|
|
59
59
|
var FrameType = {
|
|
@@ -96,236 +96,12 @@ var PairFrameTypes = [
|
|
|
96
96
|
FrameType.PV_Next
|
|
97
97
|
];
|
|
98
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
99
|
// src/pairing.ts
|
|
321
100
|
import { AccessoryPair } from "@basmilius/apple-common";
|
|
322
101
|
class CompanionLinkPairing {
|
|
323
102
|
get internal() {
|
|
324
103
|
return this.#internal;
|
|
325
104
|
}
|
|
326
|
-
get socket() {
|
|
327
|
-
return this.#protocol.socket;
|
|
328
|
-
}
|
|
329
105
|
#internal;
|
|
330
106
|
#protocol;
|
|
331
107
|
constructor(protocol) {
|
|
@@ -343,7 +119,7 @@ class CompanionLinkPairing {
|
|
|
343
119
|
}
|
|
344
120
|
async#request(step, data) {
|
|
345
121
|
const frameType = step === "m1" ? FrameType.PS_Start : FrameType.PS_Next;
|
|
346
|
-
const [, response] = await this.socket.exchange(frameType, {
|
|
122
|
+
const [, response] = await this.#protocol.socket.exchange(frameType, {
|
|
347
123
|
_pd: data,
|
|
348
124
|
_pwTy: 1
|
|
349
125
|
});
|
|
@@ -355,9 +131,9 @@ class CompanionLinkPairing {
|
|
|
355
131
|
}
|
|
356
132
|
|
|
357
133
|
// src/socket.ts
|
|
358
|
-
import { randomInt
|
|
359
|
-
import { Chacha20, ENCRYPTION, EncryptionAwareConnection, reporter
|
|
360
|
-
import { OPack
|
|
134
|
+
import { randomInt } from "node:crypto";
|
|
135
|
+
import { Chacha20, ENCRYPTION, EncryptionAwareConnection, reporter } from "@basmilius/apple-common";
|
|
136
|
+
import { OPack } from "@basmilius/apple-encoding";
|
|
361
137
|
var HEADER_BYTES = 4;
|
|
362
138
|
|
|
363
139
|
class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
@@ -369,7 +145,7 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
369
145
|
#xid;
|
|
370
146
|
constructor(address, port) {
|
|
371
147
|
super(address, port);
|
|
372
|
-
this.#xid =
|
|
148
|
+
this.#xid = randomInt(0, 2 ** 16);
|
|
373
149
|
this.onData = this.onData.bind(this);
|
|
374
150
|
this.on("data", this.onData);
|
|
375
151
|
}
|
|
@@ -386,8 +162,8 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
386
162
|
}
|
|
387
163
|
async send(type, obj) {
|
|
388
164
|
const _x = this.#xid++;
|
|
389
|
-
obj._x ??=
|
|
390
|
-
let payload = Buffer.from(
|
|
165
|
+
obj._x ??= OPack.sizedInt(_x, 8);
|
|
166
|
+
let payload = Buffer.from(OPack.encode(obj));
|
|
391
167
|
let payloadLength = payload.byteLength;
|
|
392
168
|
if (this.isEncrypted && payloadLength > 0) {
|
|
393
169
|
payloadLength += Chacha20.CHACHA20_AUTH_TAG_LENGTH;
|
|
@@ -404,16 +180,16 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
404
180
|
} else {
|
|
405
181
|
data = Buffer.concat([header, payload]);
|
|
406
182
|
}
|
|
407
|
-
|
|
183
|
+
reporter.raw("Sending data frame...", this.isEncrypted, Buffer.from(data).toString("hex"), obj);
|
|
408
184
|
try {
|
|
409
185
|
return await this.write(data);
|
|
410
186
|
} catch (err) {
|
|
411
|
-
|
|
187
|
+
reporter.error("Error in Companion Link send()", err);
|
|
412
188
|
this.emit("error", err);
|
|
413
189
|
}
|
|
414
190
|
}
|
|
415
191
|
async onData(buffer) {
|
|
416
|
-
|
|
192
|
+
reporter.raw("Received data frame", buffer.toString("hex"));
|
|
417
193
|
this.#buffer = Buffer.concat([this.#buffer, buffer]);
|
|
418
194
|
try {
|
|
419
195
|
while (this.#buffer.byteLength >= HEADER_BYTES) {
|
|
@@ -421,19 +197,19 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
421
197
|
const payloadLength = header.readUintBE(1, 3);
|
|
422
198
|
const totalLength = HEADER_BYTES + payloadLength;
|
|
423
199
|
if (this.#buffer.byteLength < totalLength) {
|
|
424
|
-
|
|
200
|
+
reporter.warn(`Not enough data yet, waiting on the next frame.. needed=${totalLength} available=${this.#buffer.byteLength} receivedLength=${buffer.byteLength}`);
|
|
425
201
|
return;
|
|
426
202
|
}
|
|
427
|
-
|
|
203
|
+
reporter.raw(`Frame found length=${totalLength} availableLength=${this.#buffer.byteLength} receivedLength=${buffer.byteLength}`);
|
|
428
204
|
const frame = Buffer.from(this.#buffer.subarray(0, totalLength));
|
|
429
205
|
this.#buffer = this.#buffer.subarray(totalLength);
|
|
430
|
-
|
|
206
|
+
reporter.raw(`Handle frame, ${this.#buffer.byteLength} bytes left...`);
|
|
431
207
|
const data = await this.#decrypt(frame);
|
|
432
208
|
let payload = data.subarray(4, totalLength);
|
|
433
209
|
await this.#handle(header, payload);
|
|
434
210
|
}
|
|
435
211
|
} catch (err) {
|
|
436
|
-
|
|
212
|
+
reporter.error("Error in Companion Link onData handler", err);
|
|
437
213
|
this.emit("error", err);
|
|
438
214
|
}
|
|
439
215
|
}
|
|
@@ -454,11 +230,11 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
454
230
|
async#handle(header, payload) {
|
|
455
231
|
const type = header.readInt8();
|
|
456
232
|
if (!OPackFrameTypes.includes(type)) {
|
|
457
|
-
|
|
233
|
+
reporter.warn("Packet not handled, no opack frame.");
|
|
458
234
|
return;
|
|
459
235
|
}
|
|
460
|
-
payload =
|
|
461
|
-
|
|
236
|
+
payload = OPack.decode(payload);
|
|
237
|
+
reporter.raw("Decoded OPACK", { header, payload });
|
|
462
238
|
if ("_x" in payload) {
|
|
463
239
|
const _x = payload._x;
|
|
464
240
|
if (_x in this.#queue) {
|
|
@@ -480,7 +256,7 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
480
256
|
resolve?.([header, payload]);
|
|
481
257
|
delete this.#queue[_x];
|
|
482
258
|
} else {
|
|
483
|
-
|
|
259
|
+
reporter.warn("No handler for message", [header, payload]);
|
|
484
260
|
}
|
|
485
261
|
}
|
|
486
262
|
}
|
|
@@ -488,9 +264,6 @@ class CompanionLinkSocket extends EncryptionAwareConnection {
|
|
|
488
264
|
// src/verify.ts
|
|
489
265
|
import { AccessoryVerify, hkdf } from "@basmilius/apple-common";
|
|
490
266
|
class CompanionLinkVerify {
|
|
491
|
-
get socket() {
|
|
492
|
-
return this.#protocol.socket;
|
|
493
|
-
}
|
|
494
267
|
#internal;
|
|
495
268
|
#protocol;
|
|
496
269
|
constructor(protocol) {
|
|
@@ -522,7 +295,7 @@ class CompanionLinkVerify {
|
|
|
522
295
|
}
|
|
523
296
|
async#request(step, data) {
|
|
524
297
|
const frameType = step === "m1" ? FrameType.PV_Start : FrameType.PV_Next;
|
|
525
|
-
const [, response] = await this.socket.exchange(frameType, {
|
|
298
|
+
const [, response] = await this.#protocol.socket.exchange(frameType, {
|
|
526
299
|
_pd: data,
|
|
527
300
|
_auTy: 4
|
|
528
301
|
});
|
|
@@ -535,9 +308,6 @@ class CompanionLinkVerify {
|
|
|
535
308
|
|
|
536
309
|
// src/protocol.ts
|
|
537
310
|
class CompanionLink {
|
|
538
|
-
get api() {
|
|
539
|
-
return this.#api;
|
|
540
|
-
}
|
|
541
311
|
get device() {
|
|
542
312
|
return this.#device;
|
|
543
313
|
}
|
|
@@ -550,7 +320,6 @@ class CompanionLink {
|
|
|
550
320
|
get verify() {
|
|
551
321
|
return this.#verify;
|
|
552
322
|
}
|
|
553
|
-
#api;
|
|
554
323
|
#device;
|
|
555
324
|
#socket;
|
|
556
325
|
#pairing;
|
|
@@ -558,7 +327,6 @@ class CompanionLink {
|
|
|
558
327
|
constructor(device) {
|
|
559
328
|
this.#device = device;
|
|
560
329
|
this.#socket = new CompanionLinkSocket(device.address, device.service.port);
|
|
561
|
-
this.#api = new CompanionLinkApi(this);
|
|
562
330
|
this.#pairing = new CompanionLinkPairing(this);
|
|
563
331
|
this.#verify = new CompanionLinkVerify(this);
|
|
564
332
|
}
|
|
@@ -568,6 +336,216 @@ class CompanionLink {
|
|
|
568
336
|
async disconnect() {
|
|
569
337
|
await this.#socket.disconnect();
|
|
570
338
|
}
|
|
339
|
+
async fetchMediaControlStatus() {
|
|
340
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
341
|
+
_i: "FetchMediaControlStatus",
|
|
342
|
+
_t: MessageType.Request,
|
|
343
|
+
_c: {}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
async fetchNowPlayingInfo() {
|
|
347
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
348
|
+
_i: "FetchCurrentNowPlayingInfoEvent",
|
|
349
|
+
_t: MessageType.Request,
|
|
350
|
+
_c: {}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
async fetchSupportedActions() {
|
|
354
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
355
|
+
_i: "FetchSupportedActionsEvent",
|
|
356
|
+
_t: MessageType.Request,
|
|
357
|
+
_c: {}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async getAttentionState() {
|
|
361
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
362
|
+
_i: "FetchAttentionState",
|
|
363
|
+
_t: MessageType.Request,
|
|
364
|
+
_c: {}
|
|
365
|
+
});
|
|
366
|
+
const { _c } = objectOrFail(payload);
|
|
367
|
+
return convertAttentionState(_c.state);
|
|
368
|
+
}
|
|
369
|
+
async getLaunchableApps() {
|
|
370
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
371
|
+
_i: "FetchLaunchableApplicationsEvent",
|
|
372
|
+
_t: MessageType.Request,
|
|
373
|
+
_c: {}
|
|
374
|
+
});
|
|
375
|
+
const { _c } = objectOrFail(payload);
|
|
376
|
+
return Object.entries(_c).map(([bundleId, name]) => ({
|
|
377
|
+
bundleId,
|
|
378
|
+
name
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
async getSiriRemoteInfo() {
|
|
382
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
383
|
+
_i: "FetchSiriRemoteInfo",
|
|
384
|
+
_t: MessageType.Request,
|
|
385
|
+
_c: {}
|
|
386
|
+
});
|
|
387
|
+
return Plist.parse(Buffer.from(payload["_c"]["SiriRemoteInfoKey"]).buffer);
|
|
388
|
+
}
|
|
389
|
+
async getUserAccounts() {
|
|
390
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
391
|
+
_i: "FetchUserAccountsEvent",
|
|
392
|
+
_t: MessageType.Request,
|
|
393
|
+
_c: {}
|
|
394
|
+
});
|
|
395
|
+
const { _c } = objectOrFail(payload);
|
|
396
|
+
return Object.entries(_c).map(([accountId, name]) => ({
|
|
397
|
+
accountId,
|
|
398
|
+
name
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
async hidCommand(command, down = false) {
|
|
402
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
403
|
+
_i: "_hidC",
|
|
404
|
+
_t: MessageType.Request,
|
|
405
|
+
_c: {
|
|
406
|
+
_hBtS: down ? 1 : 2,
|
|
407
|
+
_hidC: HidCommand[command]
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
async launchApp(bundleId) {
|
|
412
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
413
|
+
_i: "_launchApp",
|
|
414
|
+
_t: MessageType.Request,
|
|
415
|
+
_c: {
|
|
416
|
+
_bundleID: bundleId
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
async launchUrl(url) {
|
|
421
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
422
|
+
_i: "_launchApp",
|
|
423
|
+
_t: MessageType.Request,
|
|
424
|
+
_c: {
|
|
425
|
+
_urlS: url
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async mediaControlCommand(command, content) {
|
|
430
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
431
|
+
_i: "_mcc",
|
|
432
|
+
_t: MessageType.Request,
|
|
433
|
+
_c: {
|
|
434
|
+
_mcc: MediaControlCommand[command],
|
|
435
|
+
...content || {}
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return objectOrFail(payload);
|
|
439
|
+
}
|
|
440
|
+
async pressButton(command, type = "SingleTap", holdDelayMs = 500) {
|
|
441
|
+
switch (type) {
|
|
442
|
+
case "DoubleTap":
|
|
443
|
+
await this.hidCommand(command, true);
|
|
444
|
+
await this.hidCommand(command, false);
|
|
445
|
+
await this.hidCommand(command, true);
|
|
446
|
+
await this.hidCommand(command, false);
|
|
447
|
+
break;
|
|
448
|
+
case "Hold":
|
|
449
|
+
await this.hidCommand(command, true);
|
|
450
|
+
await waitFor(holdDelayMs);
|
|
451
|
+
await this.hidCommand(command, false);
|
|
452
|
+
break;
|
|
453
|
+
case "SingleTap":
|
|
454
|
+
await this.hidCommand(command, true);
|
|
455
|
+
await this.hidCommand(command, false);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async switchUserAccount(accountId) {
|
|
460
|
+
await this.#socket.exchange(FrameType.E_OPACK, {
|
|
461
|
+
_i: "SwitchUserAccountEvent",
|
|
462
|
+
_t: MessageType.Request,
|
|
463
|
+
_c: {
|
|
464
|
+
SwitchAccountID: accountId
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
async _subscribe(event, fn) {
|
|
469
|
+
this.#socket.on(event, fn);
|
|
470
|
+
await this.#socket.send(FrameType.E_OPACK, {
|
|
471
|
+
_i: "_interest",
|
|
472
|
+
_t: MessageType.Event,
|
|
473
|
+
_c: {
|
|
474
|
+
_regEvents: [event]
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
async _unsubscribe(event, fn) {
|
|
479
|
+
if (fn) {
|
|
480
|
+
this.#socket.off(event, fn);
|
|
481
|
+
}
|
|
482
|
+
await this.#socket.send(FrameType.E_OPACK, {
|
|
483
|
+
_i: "_interest",
|
|
484
|
+
_t: MessageType.Event,
|
|
485
|
+
_c: {
|
|
486
|
+
_deregEvents: [event]
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async _sessionStart() {
|
|
491
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
492
|
+
_i: "_sessionStart",
|
|
493
|
+
_t: MessageType.Request,
|
|
494
|
+
_c: {
|
|
495
|
+
_srvT: "com.apple.tvremoteservices",
|
|
496
|
+
_sid: randomInt2(0, 2 ** 32 - 1)
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
return objectOrFail(payload);
|
|
500
|
+
}
|
|
501
|
+
async _systemInfo(pairingId) {
|
|
502
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
503
|
+
_i: "_systemInfo",
|
|
504
|
+
_t: MessageType.Request,
|
|
505
|
+
_c: {
|
|
506
|
+
_bf: 0,
|
|
507
|
+
_cf: 512,
|
|
508
|
+
_clFl: 128,
|
|
509
|
+
_i: "cafecafecafe",
|
|
510
|
+
_idsID: pairingId.toString(),
|
|
511
|
+
_pubID: "FF:70:79:61:74:76",
|
|
512
|
+
_sf: 256,
|
|
513
|
+
_sv: "170.18",
|
|
514
|
+
model: "iPhone10,6",
|
|
515
|
+
name: "Bas Companion Link"
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
return objectOrFail(payload);
|
|
519
|
+
}
|
|
520
|
+
async _touchStart() {
|
|
521
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
522
|
+
_i: "_touchStart",
|
|
523
|
+
_t: MessageType.Request,
|
|
524
|
+
_c: {
|
|
525
|
+
_height: OPack2.float(1000),
|
|
526
|
+
_tFl: 0,
|
|
527
|
+
_width: OPack2.float(1000)
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
return objectOrFail(payload);
|
|
531
|
+
}
|
|
532
|
+
async _tvrcSessionStart() {
|
|
533
|
+
const [, payload] = await this.#socket.exchange(FrameType.E_OPACK, {
|
|
534
|
+
_i: "TVRCSessionStart",
|
|
535
|
+
_t: MessageType.Request,
|
|
536
|
+
_btHP: false,
|
|
537
|
+
_inUseProc: "tvremoted",
|
|
538
|
+
_c: {}
|
|
539
|
+
});
|
|
540
|
+
return objectOrFail(payload);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function objectOrFail(obj) {
|
|
544
|
+
if (typeof obj === "object") {
|
|
545
|
+
return obj;
|
|
546
|
+
}
|
|
547
|
+
reporter2.error("Expected an object.", { obj });
|
|
548
|
+
throw new Error("Expected an object.");
|
|
571
549
|
}
|
|
572
550
|
export {
|
|
573
551
|
convertAttentionState,
|
package/dist/pairing.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { type AccessoryCredentials, type AccessoryKeys, AccessoryPair } from "@basmilius/apple-common";
|
|
2
|
-
import type Socket from "./socket";
|
|
3
2
|
import type Protocol from "./protocol";
|
|
4
3
|
export default class CompanionLinkPairing {
|
|
5
4
|
#private;
|
|
6
5
|
get internal(): AccessoryPair;
|
|
7
|
-
get socket(): Socket;
|
|
8
6
|
constructor(protocol: Protocol);
|
|
9
7
|
start(): Promise<void>;
|
|
10
8
|
pin(askPin: () => Promise<string>): Promise<AccessoryCredentials>;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import
|
|
1
|
+
import { type DiscoveryResult } from "@basmilius/apple-common";
|
|
2
|
+
import type { AttentionState, ButtonPressType, LaunchableApp, UserAccount } from "./types";
|
|
3
|
+
import { type HidCommandKey, type MediaControlCommandKey } from "./const";
|
|
3
4
|
import Pairing from "./pairing";
|
|
4
5
|
import Socket from "./socket";
|
|
5
6
|
import Verify from "./verify";
|
|
6
7
|
export default class CompanionLink {
|
|
7
8
|
#private;
|
|
8
|
-
get api(): Api;
|
|
9
9
|
get device(): DiscoveryResult;
|
|
10
10
|
get socket(): Socket;
|
|
11
11
|
get pairing(): Pairing;
|
|
@@ -13,4 +13,23 @@ export default class CompanionLink {
|
|
|
13
13
|
constructor(device: DiscoveryResult);
|
|
14
14
|
connect(): Promise<void>;
|
|
15
15
|
disconnect(): Promise<void>;
|
|
16
|
+
fetchMediaControlStatus(): Promise<void>;
|
|
17
|
+
fetchNowPlayingInfo(): Promise<void>;
|
|
18
|
+
fetchSupportedActions(): Promise<void>;
|
|
19
|
+
getAttentionState(): Promise<AttentionState>;
|
|
20
|
+
getLaunchableApps(): Promise<LaunchableApp[]>;
|
|
21
|
+
getSiriRemoteInfo(): Promise<any>;
|
|
22
|
+
getUserAccounts(): Promise<UserAccount[]>;
|
|
23
|
+
hidCommand(command: HidCommandKey, down?: boolean): Promise<void>;
|
|
24
|
+
launchApp(bundleId: string): Promise<void>;
|
|
25
|
+
launchUrl(url: string): Promise<void>;
|
|
26
|
+
mediaControlCommand(command: MediaControlCommandKey, content?: object): Promise<object>;
|
|
27
|
+
pressButton(command: HidCommandKey, type?: ButtonPressType, holdDelayMs?: number): Promise<void>;
|
|
28
|
+
switchUserAccount(accountId: string): Promise<void>;
|
|
29
|
+
_subscribe(event: string, fn: (data: unknown) => void): Promise<void>;
|
|
30
|
+
_unsubscribe(event: string, fn?: (data: unknown) => void): Promise<void>;
|
|
31
|
+
_sessionStart(): Promise<object>;
|
|
32
|
+
_systemInfo(pairingId: Buffer): Promise<object>;
|
|
33
|
+
_touchStart(): Promise<object>;
|
|
34
|
+
_tvrcSessionStart(): Promise<object>;
|
|
16
35
|
}
|
package/dist/verify.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { type AccessoryCredentials, type AccessoryKeys } from "@basmilius/apple-common";
|
|
2
2
|
import type Protocol from "./protocol";
|
|
3
|
-
import type Socket from "./socket";
|
|
4
3
|
export default class CompanionLinkVerify {
|
|
5
4
|
#private;
|
|
6
|
-
get socket(): Socket;
|
|
7
5
|
constructor(protocol: Protocol);
|
|
8
6
|
start(credentials: AccessoryCredentials): Promise<AccessoryKeys>;
|
|
9
7
|
}
|
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.96",
|
|
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.96",
|
|
44
|
+
"@basmilius/apple-encoding": "0.0.96"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@basmilius/tools": "^2.23.0",
|
package/dist/api.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type HidCommandKey, type MediaControlCommandKey } from "./const";
|
|
2
|
-
import type { AttentionState, ButtonPressType, LaunchableApp, UserAccount } from "./types";
|
|
3
|
-
import type Protocol from "./protocol";
|
|
4
|
-
import type Socket from "./socket";
|
|
5
|
-
export default class CompanionLinkApi {
|
|
6
|
-
#private;
|
|
7
|
-
get socket(): Socket;
|
|
8
|
-
constructor(protocol: Protocol);
|
|
9
|
-
fetchMediaControlStatus(): Promise<void>;
|
|
10
|
-
fetchNowPlayingInfo(): Promise<void>;
|
|
11
|
-
fetchSupportedActions(): Promise<void>;
|
|
12
|
-
getAttentionState(): Promise<AttentionState>;
|
|
13
|
-
getLaunchableApps(): Promise<LaunchableApp[]>;
|
|
14
|
-
getSiriRemoteInfo(): Promise<any>;
|
|
15
|
-
getUserAccounts(): Promise<UserAccount[]>;
|
|
16
|
-
hidCommand(command: HidCommandKey, down?: boolean): Promise<void>;
|
|
17
|
-
launchApp(bundleId: string): Promise<void>;
|
|
18
|
-
launchUrl(url: string): Promise<void>;
|
|
19
|
-
mediaControlCommand(command: MediaControlCommandKey, content?: object): Promise<object>;
|
|
20
|
-
pressButton(command: HidCommandKey, type?: ButtonPressType, holdDelayMs?: number): Promise<void>;
|
|
21
|
-
switchUserAccount(accountId: string): Promise<void>;
|
|
22
|
-
_subscribe(event: string, fn: (data: unknown) => void): Promise<void>;
|
|
23
|
-
_unsubscribe(event: string, fn?: (data: unknown) => void): Promise<void>;
|
|
24
|
-
_sessionStart(): Promise<object>;
|
|
25
|
-
_systemInfo(pairingId: Buffer): Promise<object>;
|
|
26
|
-
_touchStart(): Promise<object>;
|
|
27
|
-
_tvrcSessionStart(): Promise<object>;
|
|
28
|
-
}
|