@octalmage/node-appletv
Version:
A Node.js library for communicating with an Apple TV
188 lines (187 loc) • 7.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const net_1 = require("net");
const protobufjs_1 = require("protobufjs");
const uuid_1 = require("uuid");
const path = require("path");
const varint = require("varint");
const snake = require("snake-case");
const camelcase = require("camelcase");
const typed_events_1 = require("./typed-events");
const message_1 = require("./message");
class Connection extends typed_events_1.default {
constructor(device) {
super();
this.device = device;
this.callbacks = new Map();
this.buffer = Buffer.alloc(0);
this.socket = new net_1.Socket();
let that = this;
this.socket.on('data', (data) => {
try {
that.buffer = Buffer.concat([that.buffer, data]);
let length = varint.decode(that.buffer);
let messageBytes = that.buffer.slice(varint.decode.bytes, length + varint.decode.bytes);
if (messageBytes.length < length) {
that.emit('debug', "Message length mismatch");
return;
}
that.buffer = that.buffer.slice(length + varint.decode.bytes);
that.emit('debug', "DEBUG: <<<< Received Data=" + messageBytes.toString('hex'));
if (device.credentials && device.credentials.readKey) {
messageBytes = device.credentials.decrypt(messageBytes);
that.emit('debug', "DEBUG: Decrypted Data=" + messageBytes.toString('hex'));
}
that.decodeMessage(messageBytes)
.then(protoMessage => {
let message = new message_1.Message(protoMessage);
that.emit('message', message);
that.executeCallbacks(message.identifier, message);
})
.catch(error => {
that.emit('error', error);
});
}
catch (error) {
that.emit('error', error);
}
});
this.socket.on('connect', () => {
that.emit('connect');
that.isOpen = true;
});
this.socket.on('close', () => {
that.emit('close');
that.isOpen = false;
});
this.socket.on('error', (error) => {
that.emit('error', error);
});
}
addCallback(identifier, callback) {
if (this.callbacks.has(identifier)) {
this.callbacks.get(identifier).push({
callback: callback
});
}
else {
this.callbacks.set(identifier, [{
callback: callback
}]);
}
}
executeCallbacks(identifier, message) {
let callbacks = this.callbacks.get(identifier);
if (callbacks) {
for (var i = 0; i < callbacks.length; i++) {
let callback = callbacks[i];
callback.callback(message);
this.callbacks.get(identifier).splice(i, 1);
}
return true;
}
else {
return false;
}
}
open() {
let that = this;
return protobufjs_1.load(path.resolve(__dirname + "/protos/ProtocolMessage.proto"))
.then(root => {
that.ProtocolMessage = root.lookupType("ProtocolMessage");
return new Promise((resolve, reject) => {
that.socket.connect(this.device.port, this.device.address, function () {
resolve();
});
});
});
}
close() {
this.socket.end();
}
sendBlank(typeName, waitForResponse, credentials) {
let that = this;
return protobufjs_1.load(path.resolve(__dirname + "/protos/ProtocolMessage.proto"))
.then(root => {
let ProtocolMessage = root.lookupType("ProtocolMessage");
let types = ProtocolMessage.lookupEnum("Type");
let type = types.values[typeName];
let name = camelcase(typeName);
let message = ProtocolMessage.create({
type: type,
priority: 0
});
return that.sendProtocolMessage(message, name, type, waitForResponse, credentials);
});
}
send(message, waitForResponse, priority, credentials) {
let ProtocolMessage = message.$type.parent['ProtocolMessage'];
let types = ProtocolMessage.lookupEnum("Type");
let name = message.$type.name;
let typeName = snake(name).toUpperCase();
let type = types.values[typeName];
var outerMessage = ProtocolMessage.create({
priority: priority,
type: type
});
if (Object.keys(message.toJSON()).length > 0) {
let field = outerMessage.$type.fieldsArray.filter((f) => { return f.type == message.$type.name; })[0];
outerMessage[field.name] = message;
}
return this.sendProtocolMessage(outerMessage, name, type, waitForResponse, credentials);
}
sendProtocolMessage(message, name, type, waitForResponse, credentials) {
let that = this;
return new Promise((resolve, reject) => {
let ProtocolMessage = message.$type;
if (waitForResponse) {
let identifier = uuid_1.v4();
message["identifier"] = identifier;
let callback = (message) => {
resolve(message);
};
that.addCallback(identifier, callback);
}
let data = ProtocolMessage.encode(message).finish();
that.emit('debug', "DEBUG: >>>> Send Data=" + data.toString('hex'));
if (credentials && credentials.writeKey) {
let encrypted = credentials.encrypt(data);
that.emit('debug', "DEBUG: >>>> Send Encrypted Data=" + encrypted.toString('hex'));
that.emit('debug', "DEBUG: >>>> Send Protobuf=" + JSON.stringify(message.toJSON(), null, 2));
let messageLength = Buffer.from(varint.encode(encrypted.length));
let bytes = Buffer.concat([messageLength, encrypted]);
that.socket.write(bytes);
}
else {
that.emit('debug', "DEBUG: >>>> Send Protobuf=" + JSON.stringify(message.toJSON(), null, 2));
let messageLength = Buffer.from(varint.encode(data.length));
let bytes = Buffer.concat([messageLength, data]);
that.socket.write(bytes);
}
if (!waitForResponse) {
resolve(new message_1.Message(message));
}
});
}
decodeMessage(data) {
let that = this;
return protobufjs_1.load(path.resolve(__dirname + "/protos/ProtocolMessage.proto"))
.then(root => {
let ProtocolMessage = root.lookupType("ProtocolMessage");
let preMessage = ProtocolMessage.decode(data);
let type = preMessage.toJSON().type;
if (type == null) {
return Promise.resolve(preMessage);
}
let name = type[0].toUpperCase() + camelcase(type).substring(1);
return protobufjs_1.load(path.resolve(__dirname + "/protos/" + name + ".proto"))
.then(root => {
let ProtocolMessage = root.lookupType("ProtocolMessage");
let message = ProtocolMessage.decode(data);
that.emit('debug', "DEBUG: <<<< Received Protobuf=" + JSON.stringify(message.toJSON(), null, 2));
return message;
});
});
}
}
exports.Connection = Connection;