Finish app conversion and enforce code style

This commit is contained in:
hensm
2019-02-26 01:30:30 +00:00
parent b7571791e2
commit 1e49fbe9be
19 changed files with 541 additions and 186 deletions

15
app/@types/bplist-creator/index.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
/// <reference types="node" />
declare module "bplist-creator" {
import Buffer from "buffer";
function bplist (dicts: object): Buffer;
export = bplist;
namespace bplist {
export class Real {
public value: number;
constructor (value: number);
}
}
}

23
app/@types/bplist-parser/index.d.ts vendored Normal file
View File

@@ -0,0 +1,23 @@
/// <reference types="node" />
declare module "bplist-parser" {
import Buffer from "buffer";
export var maxObjectSize: number;
export var maxObjectCount: number;
export class UID {
constructor (id: number);
UID: number;
}
type ParseFileCallback = (
err: string
, result?: Buffer) => void;
export function parseFile (
fileNameOrBuffer: Buffer | string
, callback: ParseFileCallback): void;
export function parseBuffer (buffer: Buffer): any;
}

View File

@@ -1,16 +1,17 @@
/// <reference types="node" />
declare module "castv2" { declare module "castv2" {
import { EventEmitter } from "events"; import { EventEmitter } from "events";
interface ClientConnectOptions { interface ClientConnectOptions {
host: string host: string;
, port?: number port?: number;
} }
interface ClientConnectCallback { type CallbackFunction = () => void;
(): void;
}
export interface ClientChannel extends EventEmitter {
export interface Channel extends EventEmitter {
bus: Client; bus: Client;
sourceId: string; sourceId: string;
destinationId: string; destinationId: string;
@@ -21,41 +22,47 @@ declare module "castv2" {
close (): void; close (): void;
} }
interface ServerListenCallback { export interface DeviceAuthMessage {
(): void; parse (data: any): any;
serialize (data: any): any;
} }
export class Client extends EventEmitter { export class Client extends EventEmitter {
connect (host: string, callback?: ClientConnectCallback): void; public connect (
connect (options: ClientConnectOptions, callback: ClientConnectCallback): void; options: ClientConnectOptions | string
, callback?: CallbackFunction): void;
close (): void; public close (): void;
send (sourceId: string public send (
, destinationId: string sourceId: string
, namespace: string , destinationId: string
, data: Buffer | string): void; , namespace: string
, data: Buffer | string): void;
createChannel (sourceId: string public createChannel (
, destinationId: string sourceId: string
, namespace: string , destinationId: string
, encoding: string): ClientChannel; , namespace: string
, encoding: string): Channel;
} }
export class Server { export class Server extends EventEmitter {
constructor (options: object); constructor (options: object);
listen (port: number public listen (
port: number
, host: string , host: string
, callback: ServerListenCallback): void; , callback?: CallbackFunction): void;
send (clientId: string public send (
, sourceId: string clientId: string
, destinationId: string , sourceId: string
, namespace: string , destinationId: string
, data: Buffer | string): void; , namespace: string
, data: Buffer | string): void;
close (): void; public close (): void;
} }
} }

55
app/@types/fast-srp-hap/index.d.ts vendored Normal file
View File

@@ -0,0 +1,55 @@
/// <reference types="node" />
declare module "fast-srp-hap" {
import Buffer from "buffer";
interface Param {
N_length_bits: number;
N: any;
g: any;
hash: string;
}
export const params: { [key: number]: Param };
type GenKeyCallback = (
err: string
, buf: Buffer) => void;
export function genKey (
bytes: number
, callback: GenKeyCallback): void;
export function computeVerifier (
params: object
, salt: Buffer
, I: Buffer
, P: Buffer): Buffer;
export class Client {
constructor (
params: object
, salt_buf: Buffer
, identity_buf: Buffer
, password_buf: Buffer
, secret1_buf: Buffer);
computeA (): Buffer;
setB(B_buf: Buffer): void;
computeM1 (): Buffer;
checkM2 (serverM2_buf: Buffer): void;
computeK (): Buffer;
}
export class Server {
constructor (
params: object
, verifier_buf: Buffer
, secret2_buf: Buffer);
computeB (): Buffer;
setA (A_buf: Buffer): void;
checkM1 (clientM1_buf: Buffer): Buffer;
computeK (): Buffer;
}
}

173
app/package-lock.json generated
View File

@@ -46,6 +46,15 @@
"integrity": "sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q==", "integrity": "sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q==",
"dev": true "dev": true
}, },
"@types/node-fetch": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.1.6.tgz",
"integrity": "sha512-Hv1jgh3pfpUEl2F2mqUd1AfLSk1YbUCeBJFaP36t7esAO617dErqdxWb5cdG2NfJGOofkmBW36fdx0dVewxDRg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"ajv": { "ajv": {
"version": "5.5.2", "version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
@@ -58,6 +67,12 @@
"json-schema-traverse": "^0.3.0" "json-schema-traverse": "^0.3.0"
} }
}, },
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": { "ansi-styles": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -67,6 +82,15 @@
"color-convert": "^1.9.0" "color-convert": "^1.9.0"
} }
}, },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"arr-diff": { "arr-diff": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -166,6 +190,44 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true "dev": true
}, },
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"dev": true,
"requires": {
"chalk": "^1.1.3",
"esutils": "^2.0.2",
"js-tokens": "^3.0.2"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"babel-runtime": { "babel-runtime": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -336,6 +398,12 @@
"resolved": "https://registry.npmjs.org/bufferview/-/bufferview-1.0.1.tgz", "resolved": "https://registry.npmjs.org/bufferview/-/bufferview-1.0.1.tgz",
"integrity": "sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0=" "integrity": "sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0="
}, },
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
},
"byline": { "byline": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
@@ -468,6 +536,12 @@
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
} }
}, },
"commander": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true
},
"component-emitter": { "component-emitter": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
@@ -594,6 +668,12 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true "dev": true
}, },
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"dir-glob": { "dir-glob": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
@@ -1024,6 +1104,15 @@
"har-schema": "^2.0.0" "har-schema": "^2.0.0"
} }
}, },
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -1274,6 +1363,30 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true "dev": true
}, },
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
},
"js-yaml": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz",
"integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"dependencies": {
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
}
}
},
"jsbn": { "jsbn": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -1837,6 +1950,15 @@
"throttleit": "^1.0.0" "throttleit": "^1.0.0"
} }
}, },
"resolve": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
},
"resolve-url": { "resolve-url": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -2049,6 +2171,12 @@
"extend-shallow": "^3.0.0" "extend-shallow": "^3.0.0"
} }
}, },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"sshpk": { "sshpk": {
"version": "1.15.2", "version": "1.15.2",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
@@ -2124,6 +2252,15 @@
"integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
"dev": true "dev": true
}, },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"supports-color": { "supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -2190,6 +2327,42 @@
"punycode": "^1.4.1" "punycode": "^1.4.1"
} }
}, },
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
"tslint": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.0.tgz",
"integrity": "sha512-ECOOQRxXCYnUUePG5h/+Z1Zouobk3KFpIHA9aKBB/nnMxs97S1JJPDGt5J4cGm1y9U9VmVlfboOxA8n1kSNzGw==",
"dev": true,
"requires": {
"babel-code-frame": "^6.22.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^3.2.0",
"glob": "^7.1.1",
"js-yaml": "^3.7.0",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.27.2"
}
},
"tsutils": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"tunnel-agent": { "tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",

View File

@@ -8,7 +8,8 @@
"build": "node bin/build.js", "build": "node bin/build.js",
"package": "node bin/build.js --package", "package": "node bin/build.js --package",
"install-manifest": "node bin/install-manifest.js", "install-manifest": "node bin/install-manifest.js",
"remove-manifest": "node bin/install-manifest.js --remove" "remove-manifest": "node bin/install-manifest.js --remove",
"lint": "tslint -c tslint.json -p ."
}, },
"dependencies": { "dependencies": {
"bplist-creator": "0.0.7", "bplist-creator": "0.0.7",
@@ -24,8 +25,10 @@
"@types/dnssd": "^0.4.1", "@types/dnssd": "^0.4.1",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
"@types/node": "^11.9.5", "@types/node": "^11.9.5",
"@types/node-fetch": "^2.1.6",
"mustache": "^3.0.1", "mustache": "^3.0.1",
"pkg": "^4.3.5" "pkg": "^4.3.5",
"tslint": "^5.13.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"rage-edit": "^1.2.0" "rage-edit": "^1.2.0"

View File

@@ -25,25 +25,24 @@ export interface UpdateMessageData {
export default class Media { export default class Media {
private sessionId: number; private sessionId: number;
private mediaSessionId: number; private mediaSessionId: number;
private _id: string; private referenceId: string;
private session: Session; private session: Session;
private channel: any; private channel: any;
private _sendMessage: SendMessageCallback; private sendMessageCallback: SendMessageCallback;
constructor (sessionId: number constructor (
, mediaSessionId: number sessionId: number
, _id: string , mediaSessionId: number
, parentSession: Session, , referenceId: string
_sendMessage: SendMessageCallback) { , session: Session
, sendMessageCallback: SendMessageCallback) {
this._id = _id;
this._sendMessage = _sendMessage;
this.sessionId = sessionId; this.sessionId = sessionId;
this.mediaSessionId = mediaSessionId; this.mediaSessionId = mediaSessionId;
this.referenceId = referenceId;
this.session = session;
this.sendMessageCallback = sendMessageCallback;
this.session = parentSession;
this.session.createChannel(MEDIA_NAMESPACE); this.session.createChannel(MEDIA_NAMESPACE);
this.channel = this.session.channelMap.get(MEDIA_NAMESPACE); this.channel = this.session.channelMap.get(MEDIA_NAMESPACE);
@@ -54,11 +53,12 @@ export default class Media {
const status = data.status[0]; const status = data.status[0];
const messageData = { const messageData = {
currentTime: status.currentTime _lastCurrentTime: Date.now() / 1000
, _lastCurrentTime: Date.now() / 1000
, customData: status.customData
, _volumeLevel: status.volume.level , _volumeLevel: status.volume.level
, _volumeMuted: status.volume.muted , _volumeMuted: status.volume.muted
, currentTime: status.currentTime
, customData: status.customData
, playbackRate: status.playbackRate , playbackRate: status.playbackRate
, playerState: status.playerState , playerState: status.playerState
, repeatMode: status.repeatMode , repeatMode: status.repeatMode
@@ -81,7 +81,7 @@ export default class Media {
}); });
} }
messageHandler (message: Message) { public messageHandler (message: Message) {
switch (message.subject) { switch (message.subject) {
case "bridge:/media/sendMediaMessage": { case "bridge:/media/sendMediaMessage": {
let error = false; let error = false;
@@ -97,15 +97,15 @@ export default class Media {
}); });
break; break;
}; }
} }
} }
sendMessage (subject: string, data: any = {}) { private sendMessage (subject: string, data: any = {}) {
this._sendMessage({ this.sendMessageCallback({
subject subject
, data , data
, _id: this._id , _id: this.referenceId
}); });
} }
} }

View File

@@ -1,6 +1,8 @@
"use strict"; "use strict";
import { Client, ClientChannel } from "castv2"; import uuid from "uuid";
import { Channel, Client } from "castv2";
import { Message import { Message
, SendMessageCallback } from "./types"; , SendMessageCallback } from "./types";
@@ -11,40 +13,39 @@ const NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat";
const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver"; const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver";
export default class Session { export default class Session {
private _sendMessage: SendMessageCallback; public channelMap = new Map<string, Channel>();
private sendMessageCallback: SendMessageCallback;
private sessionId: number; private sessionId: number;
private _id: string; private referenceId: string;
private client: Client; private client: Client;
private clientConnection: ClientChannel; private clientConnection: Channel;
private clientHeartbeat: ClientChannel; private clientHeartbeat: Channel;
private clientReceiver: ClientChannel; private clientReceiver: Channel;
private clientHeartbeatIntervalId: NodeJS.Timer; private clientHeartbeatIntervalId: NodeJS.Timer;
private isSessionCreated = false; private isSessionCreated = false;
private clientId: string; private clientId: string;
private transportId: string; private transportId: string;
private transportConnection: ClientChannel; private transportConnection: Channel;
private app: any; private app: any;
constructor (
host: string
, port: number
, appId: string
, sessionId: number
, sendMessageCallback: SendMessageCallback) {
public channelMap = new Map<string, ClientChannel>(); this.sendMessageCallback = sendMessageCallback;
constructor (host: string
, port: number
, appId: string
, sessionId: number
, _sendMessage: SendMessageCallback) {
this._sendMessage = _sendMessage;
this.sessionId = sessionId; this.sessionId = sessionId;
this.client = new Client(); this.client = new Client();
this.client.connect({ host, port }, () => { this.client.connect({ host, port }, () => {
let transportHeartbeat: ClientChannel; let transportHeartbeat: Channel;
const sourceId = "sender-0"; const sourceId = "sender-0";
const destinationId = "receiver-0"; const destinationId = "receiver-0";
@@ -63,6 +64,7 @@ export default class Session {
if (transportHeartbeat) { if (transportHeartbeat) {
transportHeartbeat.send({ type: "PING" }); transportHeartbeat.send({ type: "PING" });
} }
this.clientHeartbeat.send({ type: "PING" }); this.clientHeartbeat.send({ type: "PING" });
}, 5000); }, 5000);
@@ -73,56 +75,58 @@ export default class Session {
}); });
this.clientReceiver.on("message", (message: any) => { this.clientReceiver.on("message", (message: any) => {
switch (message.type) { if (message.type === "RECEIVER_STATUS") {
case "RECEIVER_STATUS": { this.sendMessage("shim:/session/updateStatus"
this.sendMessage("shim:/session/updateStatus", message.status); , message.status);
if (!message.status.applications) return; if (!message.status.applications) {
return;
}
const receiverApp = message.status.applications[0]; const receiverApp = message.status.applications[0];
const receiverAppId = receiverApp.appId; const receiverAppId = receiverApp.appId;
this.app = receiverApp; this.app = receiverApp;
if (receiverAppId !== appId) { if (receiverAppId !== appId) {
// Close session // Close session
this.sendMessage("shim:/session/stopped"); this.sendMessage("shim:/session/stopped");
this.client.close(); this.client.close();
clearInterval(this.clientHeartbeatIntervalId); clearInterval(this.clientHeartbeatIntervalId);
return; return;
} }
if (!this.isSessionCreated) { if (!this.isSessionCreated) {
this.isSessionCreated = true; this.isSessionCreated = true;
this.transportId = this.app.transportId; this.transportId = this.app.transportId;
this.clientId = `client-${Math.floor(Math.random() * 10e5)}`; this.clientId =
`client-${Math.floor(Math.random() * 10e5)}`;
this.transportConnection = this.client.createChannel( this.transportConnection = this.client.createChannel(
this.clientId, this.transportId, NS_CONNECTION, "JSON"); this.clientId, this.transportId
transportHeartbeat = this.client.createChannel( , NS_CONNECTION, "JSON");
this.clientId, this.transportId, NS_HEARTBEAT, "JSON"); transportHeartbeat = this.client.createChannel(
this.clientId, this.transportId
, NS_HEARTBEAT, "JSON");
this.transportConnection.send({ type: "CONNECT" }); this.transportConnection.send({ type: "CONNECT" });
this.sessionId = this.app.sessionId; this.sessionId = this.app.sessionId;
this.sendMessage("shim:/session/connected", { this.sendMessage("shim:/session/connected", {
sessionId: this.app.sessionId sessionId: this.app.sessionId
, namespaces: this.app.namespaces , namespaces: this.app.namespaces
, displayName: this.app.displayName , displayName: this.app.displayName
, statusText: this.app.displayName , statusText: this.app.displayName
}); });
} }
break;
};
} }
}); });
}); });
} }
messageHandler (message: Message) { public messageHandler (message: Message) {
switch (message.subject) { switch (message.subject) {
case "bridge:/session/close": case "bridge:/session/close":
this.close(); this.close();
@@ -136,7 +140,7 @@ export default class Session {
this._impl_sendMessage( this._impl_sendMessage(
message.data.namespace message.data.namespace
, message.data.message , message.data.message
, message.data.messageId) , message.data.messageId);
break; break;
case "bridge:/session/impl_setReceiverMuted": case "bridge:/session/impl_setReceiverMuted":
@@ -157,15 +161,7 @@ export default class Session {
} }
} }
sendMessage (subject: string, data: any = {}) { public createChannel (namespace: string) {
this._sendMessage({
subject
, data
, _id: this._id
});
}
createChannel (namespace: string) {
if (!this.channelMap.has(namespace)) { if (!this.channelMap.has(namespace)) {
this.channelMap.set(namespace this.channelMap.set(namespace
, this.client.createChannel( , this.client.createChannel(
@@ -173,25 +169,36 @@ export default class Session {
} }
} }
close () { public close () {
this.clientConnection.send({ type: "CLOSE" }); this.clientConnection.send({ type: "CLOSE" });
if (this.transportConnection) { if (this.transportConnection) {
this.transportConnection.send({ type: "CLOSE" }); this.transportConnection.send({ type: "CLOSE" });
} }
} }
private sendMessage (subject: string, data: any = {}) {
this.sendMessageCallback({
subject
, data
, _id: this.referenceId
});
}
_impl_addMessageListener (namespace: string) { private _impl_addMessageListener (namespace: string) {
this.createChannel(namespace); this.createChannel(namespace);
this.channelMap.get(namespace).on("message", (data: any) => { this.channelMap.get(namespace).on("message", (data: any) => {
this.sendMessage("shim:/session/impl_addMessageListener", { this.sendMessage("shim:/session/impl_addMessageListener", {
namespace: namespace namespace
, data: JSON.stringify(data) , data: JSON.stringify(data)
}); });
}) });
} }
_impl_sendMessage (namespace: string, message: object, messageId: string) { private _impl_sendMessage (
namespace: string
, message: object
, messageId: string) {
let error = false; let error = false;
try { try {
@@ -207,7 +214,8 @@ export default class Session {
}); });
} }
_impl_setReceiverMuted (muted: boolean, volumeId: string) { private _impl_setReceiverMuted (muted: boolean, volumeId: string) {
let error = false; let error = false;
try { try {
@@ -226,7 +234,8 @@ export default class Session {
}); });
} }
_impl_setReceiverVolumeLevel (newLevel: number, volumeId: string) { private _impl_setReceiverVolumeLevel (newLevel: number, volumeId: string) {
let error = false; let error = false;
try { try {
@@ -234,7 +243,7 @@ export default class Session {
type: "SET_VOLUME" type: "SET_VOLUME"
, volume: { level: newLevel } , volume: { level: newLevel }
, requestId: 0 , requestId: 0
}) });
} catch (err) { } catch (err) {
error = true; error = true;
} }
@@ -245,7 +254,7 @@ export default class Session {
}); });
} }
_impl_stop (stopId: string) { private _impl_stop (stopId: string) {
let error = false; let error = false;
try { try {

View File

@@ -8,10 +8,10 @@
* - https://github.com/postlund/pyatv/blob/master/docs/airplay.rst * - https://github.com/postlund/pyatv/blob/master/docs/airplay.rst
*/ */
import nacl from "tweetnacl";
import srp6a from "fast-srp-hap";
import crypto from "crypto"; import crypto from "crypto";
import fetch from "node-fetch"; import srp6a from "fast-srp-hap";
import fetch, { Headers } from "node-fetch";
import nacl from "tweetnacl";
import bplist from "./bplist"; import bplist from "./bplist";
@@ -22,7 +22,11 @@ const MIMETYPE_BPLIST = "application/x-apple-binary-plist";
* Client ID and keypair * Client ID and keypair
*/ */
export class AirPlayAuthCredentials { export class AirPlayAuthCredentials {
constructor (clientId, clientSk) { public clientId: string;
public clientSk: Uint8Array;
public clientPk: Uint8Array;
constructor (clientId: string, clientSk: Uint8Array) {
if (clientId && clientSk) { if (clientId && clientSk) {
this.clientId = clientId; this.clientId = clientId;
this.clientSk = clientSk; this.clientSk = clientSk;
@@ -40,11 +44,13 @@ export class AirPlayAuthCredentials {
} }
export class AirPlayAuth { export class AirPlayAuth {
constructor (address, credentials) { private address: string;
private credentials: AirPlayAuthCredentials;
private baseUrl: URL;
constructor (address: string, credentials: AirPlayAuthCredentials) {
this.address = address; this.address = address;
this.clientId = credentials.clientId; this.credentials = credentials;
this.clientSk = credentials.clientSk;
this.clientPk = credentials.clientPk;
this.baseUrl = new URL(`http://${this.address}:${AIRPLAY_PORT}`); this.baseUrl = new URL(`http://${this.address}:${AIRPLAY_PORT}`);
} }
@@ -52,7 +58,7 @@ export class AirPlayAuth {
/** /**
* Begins pairing process. * Begins pairing process.
*/ */
async beginPairing () { public async beginPairing () {
return this.sendPostRequest("/pair-pin-start"); return this.sendPostRequest("/pair-pin-start");
} }
@@ -61,7 +67,7 @@ export class AirPlayAuth {
* beginPairing(). Coordinates the three pairing stages and * beginPairing(). Coordinates the three pairing stages and
* manages request responses. * manages request responses.
*/ */
async finishPairing (pin) { public async finishPairing (pin: string) {
// Stage 1 response // Stage 1 response
const { pk: serverPk const { pk: serverPk
, salt: serverSalt } = await this.pairSetupPin1(); , salt: serverSalt } = await this.pairSetupPin1();
@@ -72,11 +78,11 @@ export class AirPlayAuth {
// Create SRP client // Create SRP client
const srpClient = new srp6a.Client( const srpClient = new srp6a.Client(
srpParams // Params srpParams // Params
, serverSalt // Receiver salt , serverSalt // Receiver salt
, Buffer.from(this.clientId) // Username , Buffer.from(this.credentials.clientId) // Username
, Buffer.from(pin) // Password (receiver pin) , Buffer.from(pin) // Password (receiver pin)
, Buffer.from(this.clientSk)); // Client secret key , Buffer.from(this.credentials.clientSk)); // Client secret key
// Add receiver's public key // Add receiver's public key
srpClient.setB(serverPk); srpClient.setB(serverPk);
@@ -87,7 +93,7 @@ export class AirPlayAuth {
, srpClient.computeM1()); // SRP proof , srpClient.computeM1()); // SRP proof
// Stage 3 response // Stage 3 response
console.log(await this.pairSetupPin3(srpClient.computeK())); await this.pairSetupPin3(srpClient.computeK());
} }
/** /**
@@ -96,12 +102,12 @@ export class AirPlayAuth {
* Triggering the receiver passcode display and receiving * Triggering the receiver passcode display and receiving
* its public key / salt. * its public key / salt.
*/ */
async pairSetupPin1 () { public async pairSetupPin1 (): Promise<any> {
const [ response ] = await this.sendPostRequestBplist( const [ response ] = await this.sendPostRequestBplist(
"/pair-setup-pin" "/pair-setup-pin"
, { , {
method: "pin" method: "pin"
, user: this.clientId , user: this.credentials.clientId
}); });
return response; return response;
@@ -114,7 +120,10 @@ export class AirPlayAuth {
* public keys, sending them to the receiver and receiving its * public keys, sending them to the receiver and receiving its
* proof. * proof.
*/ */
async pairSetupPin2 (pk, proof) { public async pairSetupPin2 (
pk: Buffer
, proof: Buffer): Promise<any> {
const [ response ] = await this.sendPostRequestBplist( const [ response ] = await this.sendPostRequestBplist(
"/pair-setup-pin" "/pair-setup-pin"
, { pk, proof }); , { pk, proof });
@@ -129,7 +138,9 @@ export class AirPlayAuth {
* secret hash and sending it to the receiver. Receiver then * secret hash and sending it to the receiver. Receiver then
* responds confirming the pairing is complete. * responds confirming the pairing is complete.
*/ */
async pairSetupPin3 (sharedSecretHash) { public async pairSetupPin3 (
sharedSecretHash: crypto.BinaryLike): Promise<any> {
// Create AES key // Create AES key
const aesKey = crypto.createHash("sha512") const aesKey = crypto.createHash("sha512")
.update("Pair-Setup-AES-Key") .update("Pair-Setup-AES-Key")
@@ -150,7 +161,7 @@ export class AirPlayAuth {
const cipher = crypto.createCipheriv("aes-128-gcm", aesKey, aesIv); const cipher = crypto.createCipheriv("aes-128-gcm", aesKey, aesIv);
// Encode client public key // Encode client public key
const epk = cipher.update(this.clientPk); const epk = cipher.update(this.credentials.clientPk);
cipher.final(); cipher.final();
const authTag = cipher.getAuthTag(); const authTag = cipher.getAuthTag();
@@ -166,7 +177,11 @@ export class AirPlayAuth {
* Sends a POST request to receiver and returns the * Sends a POST request to receiver and returns the
* response. * response.
*/ */
async sendPostRequest (path, contentType, data) { public async sendPostRequest (
path: string
, contentType?: string
, data?: Buffer | string): Promise<any> {
// Create URL from base receiver URL and path // Create URL from base receiver URL and path
const requestUrl = new URL(path, this.baseUrl); const requestUrl = new URL(path, this.baseUrl);
@@ -189,24 +204,26 @@ export class AirPlayAuth {
throw new Error(`AirPlay request error: ${response.status}`); throw new Error(`AirPlay request error: ${response.status}`);
} }
return await response.arrayBuffer(); return await response.buffer();
} }
/** /**
* Encodes binary plist data, sends a POST request to * Encodes binary plist data, sends a POST request to
* receiver, then decodes and returns the response. * receiver, then decodes and returns the response.
*/ */
async sendPostRequestBplist (path, data) { public async sendPostRequestBplist (
path: string
, data?: object): Promise<any> {
// Convert data to compatible type // Convert data to compatible type
const requestBody = data const requestBody = data
? bplist.create(data) ? bplist.create(data)
: null; : undefined;
const responseArrayBuffer = await this.sendPostRequest( const response = await this.sendPostRequest(
path, MIMETYPE_BPLIST, requestBody); path, MIMETYPE_BPLIST, requestBody);
// Convert response data to Buffer for bplist-parser // Convert response data to Buffer for bplist-parser
return bplist.parse.parseBuffer( return bplist.parse.parseBuffer(response);
Buffer.from(responseArrayBuffer));
} }
} }

View File

@@ -1,4 +1,4 @@
import create from "bplist-creator"; import create from "bplist-creator";
import parse from "bplist-parser"; import parse from "bplist-parser";
export default { create, parse }; export default { create, parse };

View File

@@ -1,13 +1,14 @@
import dnssd from "dnssd"; import dnssd from "dnssd";
import http from "http"; import events from "events";
import fs from "fs"; import fs from "fs";
import path from "path"; import http from "http";
import mime from "mime-types"; import mime from "mime-types";
import path from "path";
import * as transforms from "./transforms";
import Media from "./Media"; import Media from "./Media";
import Session from "./Session"; import Session from "./Session";
import * as transforms from "./transforms";
import { Message } from "./types"; import { Message } from "./types";
@@ -15,18 +16,21 @@ import { __applicationName
, __applicationVersion } from "../package.json"; , __applicationVersion } from "../package.json";
// Increase listener limit
events.EventEmitter.defaultMaxListeners = 50;
const browser = new dnssd.Browser(dnssd.tcp("googlecast")); const browser = new dnssd.Browser(dnssd.tcp("googlecast"));
// Local media server // Local media server
let httpServer: http.Server; let httpServer: http.Server;
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
if (httpServer) httpServer.close(); if (httpServer) {
httpServer.close();
}
}); });
// Increase listener limit
require("events").EventEmitter.defaultMaxListeners = 50;
// stdin -> stdout // stdin -> stdout
process.stdin process.stdin
.pipe(transforms.decode) .pipe(transforms.decode)
@@ -40,7 +44,9 @@ process.stdin
function sendMessage (message: object) { function sendMessage (message: object) {
try { try {
transforms.encode.write(message); transforms.encode.write(message);
} catch (err) {} } catch (err) {
console.error("Failed to encode message");
}
} }
@@ -102,12 +108,12 @@ async function handleMessage (message: Message) {
case "bridge:/getInfo": { case "bridge:/getInfo": {
const extensionVersion = message.data; const extensionVersion = message.data;
return __applicationVersion; return __applicationVersion;
}; }
case "bridge:/startDiscovery": { case "bridge:/startDiscovery": {
browser.start(); browser.start();
break; break;
}; }
case "bridge:/startHttpServer": { case "bridge:/startHttpServer": {
const { filePath, port } = message.data; const { filePath, port } = message.data;
@@ -144,7 +150,7 @@ async function handleMessage (message: Message) {
, "Content-Type": contentType , "Content-Type": contentType
}); });
fs.createReadStream(filePath).pipe(res) fs.createReadStream(filePath).pipe(res);
} }
}); });
@@ -155,12 +161,14 @@ async function handleMessage (message: Message) {
}); });
break; break;
}; }
case "bridge:/stopHttpServer": { case "bridge:/stopHttpServer": {
if (httpServer) httpServer.close(); if (httpServer) {
httpServer.close();
}
break; break;
}; }
} }
} }
@@ -180,7 +188,7 @@ browser.on("serviceUp", (service: dnssd.Service) => {
browser.on("serviceDown", (service: dnssd.Service) => { browser.on("serviceDown", (service: dnssd.Service) => {
transforms.encode.write({ transforms.encode.write({
subject:"shim:/serviceDown" subject: "shim:/serviceDown"
, data: { , data: {
id: service.txt.id id: service.txt.id
} }

View File

@@ -4,9 +4,7 @@ import { Transform } from "stream";
import { Message } from "./types"; import { Message } from "./types";
interface ResponseHandlerFunction { type ResponseHandlerFunction = (message: Message) => Promise<any>;
(message: Message): Promise<any>
}
/** /**
* Takes a handler function that implements the transform * Takes a handler function that implements the transform
@@ -18,9 +16,9 @@ export const response = (handler: ResponseHandlerFunction) => new Transform({
, transform (chunk: Message, encoding, callback) { , transform (chunk: Message, encoding, callback) {
Promise.resolve(handler(chunk)) Promise.resolve(handler(chunk))
.then(response => { .then(res => {
if (response) { if (res) {
callback(null, response); callback(null, res);
} else { } else {
callback(null); callback(null);
} }
@@ -94,13 +92,13 @@ export const encode = new Transform({
writableObjectMode: true writableObjectMode: true
, transform (chunk, encoding, callback) { , transform (chunk, encoding, callback) {
const message_length = Buffer.alloc(4); const messageLength = Buffer.alloc(4);
const message = Buffer.from(JSON.stringify(chunk)); const message = Buffer.from(JSON.stringify(chunk));
// Write message length // Write message length
message_length.writeUInt32LE(message.length, 0); messageLength.writeUInt32LE(message.length, 0);
// Output joined message length and content // Output joined message length and content
callback(null, Buffer.concat([message_length, message])); callback(null, Buffer.concat([messageLength, message]));
} }
}); });

View File

@@ -6,6 +6,4 @@ export interface Message {
_id?: string; _id?: string;
} }
export interface SendMessageCallback { export type SendMessageCallback = (message: Message) => void;
(message: Message): void
}

View File

@@ -6,6 +6,7 @@
, "esModuleInterop": true , "esModuleInterop": true
, "resolveJsonModule": true , "resolveJsonModule": true
, "removeComments": true , "removeComments": true
, "downlevelIteration": true
} }
, "include": [ , "include": [
"./src/**/*" "./src/**/*"

24
app/tslint.json Normal file
View File

@@ -0,0 +1,24 @@
{
"defaultSeverity": "error"
, "extends": [
"tslint:recommended"
]
, "jsRules": {}
, "rules": {
"no-consecutive-blank-lines": false
, "arrow-parens": false
, "interface-name": false
, "max-classes-per-file": false
, "max-line-length": [ true, {
"limit": 80
, "ignore-pattern": "//"
}]
, "no-console": [ true, "log" ]
, "object-literal-sort-keys": false
, "radix": false
, "semicolon": [ true, "always" ]
, "space-before-function-paren": [ true, "always" ]
, "trailing-comma": false
}
, "rulesDirectory": []
}

View File

@@ -17,7 +17,6 @@
"preact": "^8.4.2", "preact": "^8.4.2",
"preact-compat": "^3.18.4", "preact-compat": "^3.18.4",
"ts-loader": "^5.3.3", "ts-loader": "^5.3.3",
"uuid": "^3.3.2",
"web-ext": "^2.9.1", "web-ext": "^2.9.1",
"webpack": "^4.27.0", "webpack": "^4.27.0",
"webpack-cli": "^3.1.2" "webpack-cli": "^3.1.2"

View File

@@ -1,14 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true "allowJs": true
, "sourceMap": true
, "target": "es6"
, "noImplicitAny": true
, "esModuleInterop": true , "esModuleInterop": true
, "moduleResolution": "node"
, "resolveJsonModule": true
, "removeComments": true
, "jsx": "react" , "jsx": "react"
, "lib": [ "esnext", "dom" ] , "lib": [ "esnext", "dom" ]
, "moduleResolution": "node"
, "noImplicitAny": true
, "removeComments": true
, "resolveJsonModule": true
, "sourceMap": true
, "target": "es6"
} }
} }

21
package-lock.json generated
View File

@@ -2,6 +2,21 @@
"requires": true, "requires": true,
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
"@types/node": {
"version": "11.9.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.5.tgz",
"integrity": "sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q==",
"dev": true
},
"@types/uuid": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz",
"integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
@@ -468,6 +483,12 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true "dev": true
}, },
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
},
"wcwidth": { "wcwidth": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

View File

@@ -11,9 +11,12 @@
"package:ext": "npm run package --prefix ./ext", "package:ext": "npm run package --prefix ./ext",
"test": "node test/driver.js", "test": "node test/driver.js",
"install-manifest": "npm run install-manifest --prefix ./app", "install-manifest": "npm run install-manifest --prefix ./app",
"remove-manifest": "npm run remove-manifest --prefix ./app" "remove-manifest": "npm run remove-manifest --prefix ./app",
"lint": "npm run lint:app",
"lint:app": "npm run lint --prefix ./app"
}, },
"devDependencies": { "devDependencies": {
"@types/uuid": "^3.4.4",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"glob": "^7.1.3", "glob": "^7.1.3",
"jasmine-console-reporter": "^3.1.0", "jasmine-console-reporter": "^3.1.0",
@@ -21,6 +24,7 @@
"selenium-webdriver": "^4.0.0-alpha.1", "selenium-webdriver": "^4.0.0-alpha.1",
"semver": "^5.6.0", "semver": "^5.6.0",
"typescript": "^3.3.3333", "typescript": "^3.3.3333",
"uuid": "^3.3.2",
"ws": "^6.1.2" "ws": "^6.1.2"
} }
} }