Refactor app, split components into separate files

This commit is contained in:
hensm
2018-12-06 15:15:06 +00:00
parent d3dba84186
commit 6571ed547f
3 changed files with 386 additions and 374 deletions

82
app/src/Media.js Normal file
View File

@@ -0,0 +1,82 @@
export default class Media {
messageHandler (message) {
switch (message.subject) {
case "bridge:bridgemedia/sendMediaMessage": {
let error = false;
try {
this.channel.send(message.data.message);
} catch (err) {
error = true;
}
this.sendMessage("shim:media/sendMediaMessageResponse", {
messageId: message.data.messageId
, error
});
break;
};
}
}
constructor (sessionId
, mediaSessionId
, _id
, parentSession,
_sendMessage) {
this._id = _id;
this._sendMessage = _sendMessage;
this.sessionId = sessionId;
this.mediaSessionId = mediaSessionId;
const namespace = "urn:x-cast:com.google.cast.media";
this.session = parentSession;
this.session.createChannel(namespace);
this.channel = this.session.channelMap.get(namespace);
this.channel.on("message", data => {
if (data && data.type === "MEDIA_STATUS"
&& data.status && data.status.length > 0) {
const status = data.status[0];
const messageData = {
currentTime: status.currentTime
, _lastCurrentTime: Date.now() / 1000
, customData: status.customData
, _volumeLevel: status.volume.level
, _volumeMuted: status.volume.muted
, playbackRate: status.playbackRate
, playerState: status.playerState
, repeatMode: status.repeatMode
};
if (status.media) {
messageData.media = status.media;
}
if (status.mediaSessionId) {
messageData.mediaSessionId = status.mediaSessionId;
}
this.sendMessage("shim:media/update", messageData);
// Update ID
if (status.mediaSessionId) {
this.mediaSessionId = status.mediaSessionId;
}
}
});
}
sendMessage (subject, data = {}) {
this._sendMessage({
subject
, data
, _id: this._id
});
}
}

255
app/src/Session.js Normal file
View File

@@ -0,0 +1,255 @@
import { Client } from "castv2";
export default class Session {
messageHandler (message) {
switch (message.subject) {
case "bridge:bridgesession/close":
this.close();
break;
case "bridge:bridgesession/impl_addMessageListener":
this._impl_addMessageListener(message.data.namespace);
break;
case "bridge:bridgesession/impl_sendMessage":
this._impl_sendMessage(
message.data.namespace
, message.data.message
, message.data.messageId)
break;
case "bridge:bridgesession/impl_setReceiverMuted":
this._impl_setReceiverMuted(
message.data.muted
, message.data.volumeId);
break;
case "bridge:bridgesession/impl_setReceiverVolumeLevel":
this._impl_setReceiverVolumeLevel(
message.data.newLevel
, message.data.volumeId);
break;
case "bridge:bridgesession/impl_stop":
this._impl_stop(message.data.stopId);
break;
}
}
constructor (host, port, appId, sessionId, _sendMessage) {
this._sendMessage = _sendMessage;
this.sessionId = sessionId;
this.clientConnection;
this.clientHeartbeat;
this.clientReceiver;
this.channelMap = new Map();
this.client = new Client();
this.client.connect({ host, port }, () => {
let transportHeartbeat;
this.clientConnection = this.client.createChannel(
"sender-0"
, "receiver-0"
, "urn:x-cast:com.google.cast.tp.connection"
, "JSON");
this.clientHeartbeat = this.client.createChannel(
"sender-0"
, "receiver-0"
, "urn:x-cast:com.google.cast.tp.heartbeat"
, "JSON");
this.clientReceiver = this.client.createChannel(
"sender-0"
, "receiver-0"
, "urn:x-cast:com.google.cast.receiver"
, "JSON");
this.clientConnection.send({ type: "CONNECT" });
this.clientHeartbeat.send({ type: "PING" });
this.clientHeartbeatInterval = setInterval(() => {
if (transportHeartbeat) {
transportHeartbeat.send({ type: "PING" });
}
this.clientHeartbeat.send({ type: "PING" });
}, 5000);
this.clientReceiver.send({
type: "LAUNCH"
, appId
, requestId: 1
});
let sessionCreated = false;
this.clientReceiver.on("message", (data, broadcast) => {
switch (data.type) {
case "RECEIVER_STATUS":
this.sendMessage("shim:session/updateStatus", data.status);
if (!data.status.applications) return;
const receiverApp = data.status.applications[0];
const receiverAppId = receiverApp.appId;
this.app = receiverApp;
if (receiverAppId !== appId) {
// Close session
this.sendMessage("shim:session/stopped");
this.client.close();
clearInterval(this.clientHeartbeatInterval);
return;
}
if (!sessionCreated) {
sessionCreated = true;
this.transport = this.app.transportId;
this.transportId = this.app.transportId;
this.clientId = `client-${Math.floor(Math.random() * 10e5)}`;
this.transportConnect = this.client.createChannel(
this.clientId
, this.transport
, "urn:x-cast:com.google.cast.tp.connection"
, "JSON");
this.transportConnect.send({ type: "CONNECT" });
transportHeartbeat = this.client.createChannel(
this.clientId
, this.transport
, "urn:x-cast:com.google.cast.tp.heartbeat"
, "JSON");
this.sessionId = this.app.sessionId;
this.sendMessage("shim:session/connected", {
sessionId: this.app.sessionId
, namespaces: this.app.namespaces
, displayName: this.app.displayName
, statusText: this.app.displayName
});
}
break;
}
});
});
}
sendMessage (subject, data = {}) {
this._sendMessage({
subject
, data
, _id: this._id
});
}
createChannel (namespace) {
if (!this.channelMap.has(namespace)) {
this.channelMap.set(namespace
, this.client.createChannel(
this.clientId, this.transport, namespace, "JSON"));
}
}
close () {
this.clientConnection.send({ type: "CLOSE" });
if (this.transportConnect) {
this.transportConnect.send({ type: "CLOSE" });
}
}
_impl_addMessageListener (namespace) {
this.createChannel(namespace);
this.channelMap.get(namespace).on("message", data => {
this.sendMessage("shim:session/impl_addMessageListener", {
namespace: namespace
, data: JSON.stringify(data)
});
})
}
_impl_sendMessage (namespace, message, messageId) {
let error = false;
try {
this.createChannel(namespace);
this.channelMap.get(namespace).send(message);
} catch (err) {
error = true;
}
this.sendMessage("shim:session/impl_sendMessage", {
messageId
, error
});
}
_impl_setReceiverMuted (muted, volumeId) {
let error = false;
try {
this.clientReceiver.send({
type: "SET_VOLUME"
, volume: { muted }
, requestId: 0
});
} catch (err) {
error = true;
}
this.sendMessage("shim:session/impl_setReceiverMuted", {
volumeId
, error
});
}
_impl_setReceiverVolumeLevel (newLevel, volumeId) {
let error = false;
try {
this.clientReceiver.send({
type: "SET_VOLUME"
, volume: { level: newLevel }
, requestId: 0
})
} catch (err) {
error = true;
}
this.sendMessage("shim:session/impl_setReceiverVolumeLevel", {
volumeId
, error
});
}
_impl_stop (stopId) {
let error = false;
try {
this.clientReceiver.send({
type: "STOP"
, sessionId: this.sessionId
, requestId: 0
});
} catch (err) {
error = true;
}
this.client.close();
clearInterval(this.clientHeartbeatInterval);
this.sendMessage("shim:session/impl_stop", {
stopId
, error
});
}
}

View File

@@ -1,4 +1,3 @@
import { Client } from "castv2";
import { createBrowser, tcp } from "mdns-js";
import http from "http";
@@ -6,6 +5,9 @@ import fs from "fs";
import path from "path";
import * as transforms from "./transforms";
import Media from "./Media";
import Session from "./Session";
const browser = createBrowser(tcp("googlecast"));
@@ -35,20 +37,62 @@ function sendMessage (message) {
} catch (err) {}
}
// Existing counterpart Media/Session objects
const existingSessions = new Map();
const existingMedia = new Map();
/**
* Handle incoming messages from the extension and forward them to the
* appropriate handlers.
* Handle incoming messages from the extension and forward
* them to the appropriate handlers.
*
* Initializes the counterpart objects and is responsible
* for managing existing ones.
*/
async function handleMessage (message) {
if (message.subject.startsWith("bridge:bridgemedia/")) {
Media.messageHandler(message);
if (existingMedia.has(message._id)) {
// Forward message to instance message handler
existingMedia.get(message._id).messageHandler(message);
} else {
if (message.subject.endsWith("/initialize")) {
// Get Session object media belongs to
const parentSession = existingSessions.get(
message.data._internalSessionId);
// Create Media
existingMedia.set(message._id, new Media(
message.data.sessionId
, message.data.mediaSessionId
, message._id
, parentSession
, sendMessage));
}
}
return;
}
if (message.subject.startsWith("bridge:bridgesession/")) {
Session.messageHandler(message);
if (existingSessions.has(message._id)) {
// Forward message to instance message handler
existingSessions.get(message._id).messageHandler(message);
} else {
if (message.subject.endsWith("/initialize")) {
// Create Session
existingSessions.set(message._id, new Session(
message.data.address
, message.data.port
, message.data.appId
, message.data.sessionId
, sendMessage));
}
}
return;
}
switch (message.subject) {
case "bridge:discover":
browser.discover();
@@ -151,372 +195,3 @@ browser.on("serviceDown", service => {
}
});
})*/
const sessionMap = new Map();
class Session {
static messageHandler (message) {
const { _id } = message;
let session;
if (sessionMap.has(_id)) {
session = sessionMap.get(_id);
}
switch (message.subject) {
case "bridge:bridgesession/initialize":
sessionMap.set(_id, new Session(
message.data.address
, message.data.port
, message.data.appId
, message.data.sessionId));
break;
case "bridge:bridgesession/close":
session.close();
break;
case "bridge:bridgesession/impl_addMessageListener":
session._impl_addMessageListener(message.data.namespace);
break;
case "bridge:bridgesession/impl_sendMessage":
session._impl_sendMessage(
message.data.namespace
, message.data.message
, message.data.messageId)
break;
case "bridge:bridgesession/impl_setReceiverMuted":
session._impl_setReceiverMuted(
message.data.muted
, message.data.volumeId);
break;
case "bridge:bridgesession/impl_setReceiverVolumeLevel":
session._impl_setReceiverVolumeLevel(
message.data.newLevel
, message.data.volumeId);
break;
case "bridge:bridgesession/impl_stop":
session._impl_stop(message.data.stopId);
break;
}
}
constructor (host, port, appId, sessionId) {
this.sessionId = sessionId;
this.clientConnection;
this.clientHeartbeat;
this.clientReceiver;
this.channelMap = new Map();
this.client = new Client();
this.client.connect({ host, port }, () => {
let transportHeartbeat;
this.clientConnection = this.client.createChannel(
"sender-0"
, "receiver-0"
, "urn:x-cast:com.google.cast.tp.connection"
, "JSON");
this.clientHeartbeat = this.client.createChannel(
"sender-0"
, "receiver-0"
, "urn:x-cast:com.google.cast.tp.heartbeat"
, "JSON");
this.clientReceiver = this.client.createChannel(
"sender-0"
, "receiver-0"
, "urn:x-cast:com.google.cast.receiver"
, "JSON");
this.clientConnection.send({ type: "CONNECT" });
this.clientHeartbeat.send({ type: "PING" });
this.clientHeartbeatInterval = setInterval(() => {
if (transportHeartbeat) {
transportHeartbeat.send({ type: "PING" });
}
this.clientHeartbeat.send({ type: "PING" });
}, 5000);
this.clientReceiver.send({
type: "LAUNCH"
, appId
, requestId: 1
});
let sessionCreated = false;
this.clientReceiver.on("message", (data, broadcast) => {
switch (data.type) {
case "RECEIVER_STATUS":
this.sendMessage("shim:session/updateStatus", data.status);
if (!data.status.applications) return;
const receiverApp = data.status.applications[0];
const receiverAppId = receiverApp.appId;
this.app = receiverApp;
if (receiverAppId !== appId) {
// Close session
this.sendMessage("shim:session/stopped");
this.client.close();
clearInterval(this.clientHeartbeatInterval);
return;
}
if (!sessionCreated) {
sessionCreated = true;
this.transport = this.app.transportId;
this.transportId = this.app.transportId;
this.clientId = `client-${Math.floor(Math.random() * 10e5)}`;
this.transportConnect = this.client.createChannel(
this.clientId
, this.transport
, "urn:x-cast:com.google.cast.tp.connection"
, "JSON");
this.transportConnect.send({ type: "CONNECT" });
transportHeartbeat = this.client.createChannel(
this.clientId
, this.transport
, "urn:x-cast:com.google.cast.tp.heartbeat"
, "JSON");
this.sessionId = this.app.sessionId;
this.sendMessage("shim:session/connected", {
sessionId: this.app.sessionId
, namespaces: this.app.namespaces
, displayName: this.app.displayName
, statusText: this.app.displayName
});
}
break;
}
});
});
}
sendMessage (subject, data = {}) {
sendMessage({
subject
, data
, _id: this._id
});
}
createChannel (namespace) {
if (!this.channelMap.has(namespace)) {
this.channelMap.set(namespace
, this.client.createChannel(
this.clientId, this.transport, namespace, "JSON"));
}
}
close () {
this.clientConnection.send({ type: "CLOSE" });
if (this.transportConnect) {
this.transportConnect.send({ type: "CLOSE" });
}
}
_impl_addMessageListener (namespace) {
this.createChannel(namespace);
this.channelMap.get(namespace).on("message", data => {
this.sendMessage("shim:session/impl_addMessageListener", {
namespace: namespace
, data: JSON.stringify(data)
});
})
}
_impl_sendMessage (namespace, message, messageId) {
let error = false;
try {
this.createChannel(namespace);
this.channelMap.get(namespace).send(message);
} catch (err) {
error = true;
}
this.sendMessage("shim:session/impl_sendMessage", {
messageId
, error
});
}
_impl_setReceiverMuted (muted, volumeId) {
let error = false;
try {
this.clientReceiver.send({
type: "SET_VOLUME"
, volume: { muted }
, requestId: 0
});
} catch (err) {
error = true;
}
this.sendMessage("shim:session/impl_setReceiverMuted", {
volumeId
, error
});
}
_impl_setReceiverVolumeLevel (newLevel, volumeId) {
let error = false;
try {
this.clientReceiver.send({
type: "SET_VOLUME"
, volume: { level: newLevel }
, requestId: 0
})
} catch (err) {
error = true;
}
this.sendMessage("shim:session/impl_setReceiverVolumeLevel", {
volumeId
, error
});
}
_impl_stop (stopId) {
let error = false;
try {
this.clientReceiver.send({
type: "STOP"
, sessionId: this.sessionId
, requestId: 0
});
} catch (err) {
error = true;
}
this.client.close();
clearInterval(this.clientHeartbeatInterval);
this.sendMessage("shim:session/impl_stop", {
stopId
, error
});
}
}
const mediaMap = new Map();
class Media {
static messageHandler (message) {
const { _id } = message;
let media;
if (mediaMap.has(_id)) {
media = mediaMap.get(_id);
}
switch (message.subject) {
case "bridge:bridgemedia/initialize":
mediaMap.set(_id
, new Media(
message.data.sessionId
, message.data.mediaSessionId
, _id
, message.data._internalSessionId));
break;
case "bridge:bridgemedia/sendMediaMessage":
const { messageId } = message.data;
let error = false;
try {
media.channel.send(message.data.message);
} catch (err) {
error = true;
}
media.sendMessage("shim:media/sendMediaMessageResponse", {
messageId
, error
});
break;
default:
return;
}
}
constructor (sessionId, mediaSessionId, _id, _internalSessionId) {
this._id = _id;
this.sessionId = sessionId;
this.mediaSessionId = mediaSessionId;
const namespace = "urn:x-cast:com.google.cast.media";
this.session = sessionMap.get(_internalSessionId);
this.session.createChannel(namespace);
this.channel = this.session.channelMap.get(namespace);
this.channel.on("message", data => {
if (data && data.type === "MEDIA_STATUS"
&& data.status && data.status.length > 0) {
const status = data.status[0];
const messageData = {
currentTime: status.currentTime
, _lastCurrentTime: Date.now() / 1000
, customData: status.customData
, _volumeLevel: status.volume.level
, _volumeMuted: status.volume.muted
, playbackRate: status.playbackRate
, playerState: status.playerState
, repeatMode: status.repeatMode
};
if (status.media) {
messageData.media = status.media;
}
if (status.mediaSessionId) {
messageData.mediaSessionId = status.mediaSessionId;
}
this.sendMessage("shim:media/update", messageData);
// Update ID
if (status.mediaSessionId) {
this.mediaSessionId = status.mediaSessionId;
}
}
});
}
sendMessage (subject, data = {}) {
sendMessage({
subject
, data
, _id: this._id
});
}
}