2024-12-09 00:56:55 -06:00
/* eslint-disable @typescript-eslint/no-explicit-any */
// No node module for this package, only exists in webOS environment
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const Service = __non_webpack_require__ ( 'webos-service' ) ;
// const Service = require('webos-service');
import { PlayMessage , PlaybackErrorMessage , PlaybackUpdateMessage , SeekMessage , SetSpeedMessage , SetVolumeMessage , VolumeUpdateMessage } from 'common/Packets' ;
import { DiscoveryService } from 'common/DiscoveryService' ;
import { TcpListenerService } from 'common/TcpListenerService' ;
import { WebSocketListenerService } from 'common/WebSocketListenerService' ;
import { NetworkService } from 'common/NetworkService' ;
import { Opcode } from 'common/FCastSession' ;
import * as os from 'os' ;
import * as log4js from "log4js" ;
2024-12-17 00:10:12 -06:00
import { EventEmitter } from 'events' ;
2025-01-06 20:35:57 -06:00
import { ToastIcon } from 'common/components/Toast' ;
2024-12-09 00:56:55 -06:00
export class Main {
static tcpListenerService : TcpListenerService ;
static webSocketListenerService : WebSocketListenerService ;
static discoveryService : DiscoveryService ;
static logger : log4js.Logger ;
2025-01-06 20:35:57 -06:00
static emitter : EventEmitter ;
2024-12-09 00:56:55 -06:00
static {
try {
log4js . configure ( {
appenders : {
console : { type : 'console' } ,
} ,
categories : {
default : { appenders : [ 'console' ] , level : 'info' } ,
} ,
} ) ;
Main . logger = log4js . getLogger ( ) ;
Main . logger . info ( ` OS: ${ process . platform } ${ process . arch } ` ) ;
const serviceId = 'com.futo.fcast.receiver.service' ;
const service = new Service ( serviceId ) ;
2024-12-17 00:10:12 -06:00
// Service will timeout and casting will disconnect if not forced to be kept alive
2025-01-06 20:35:57 -06:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2024-12-18 20:43:47 -06:00
let keepAlive ;
service . activityManager . create ( "keepAlive" , function ( activity ) {
keepAlive = activity ;
2024-12-17 00:10:12 -06:00
} ) ;
2024-12-09 00:56:55 -06:00
2025-01-06 20:35:57 -06:00
const voidCb = ( message : any ) = > { message . respond ( { returnValue : true , value : { } } ) ; } ;
2025-01-07 22:47:46 -06:00
const objectCb = ( message : any , value : any ) = > { message . respond ( { returnValue : true , value : value } ) ; } ;
2025-01-06 20:35:57 -06:00
let toastClosureCb = null ;
service . register ( "toast" , ( message : any ) = > {
if ( message . isSubscription ) {
2025-01-07 22:47:46 -06:00
toastClosureCb = objectCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'toast' , toastClosureCb ) ;
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled toast service subscriber' ) ;
Main . emitter . off ( 'toast' , toastClosureCb ) ;
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
2024-12-09 00:56:55 -06:00
service . register ( "getDeviceInfo" , ( message : any ) = > {
Main . logger . info ( "In getDeviceInfo callback" ) ;
message . respond ( {
returnValue : true ,
value : { name : os.hostname ( ) , addresses : NetworkService.getAllIPv4Addresses ( ) }
} ) ;
} ) ;
2025-01-06 20:35:57 -06:00
let connectClosureCb = null ;
service . register ( "connect" , ( message : any ) = > {
if ( message . isSubscription ) {
2025-01-07 22:47:46 -06:00
connectClosureCb = objectCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'connect' , connectClosureCb ) ;
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled connect service subscriber' ) ;
Main . emitter . off ( 'connect' , connectClosureCb ) ;
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
let disconnectClosureCb = null ;
service . register ( "disconnect" , ( message : any ) = > {
if ( message . isSubscription ) {
2025-01-07 22:47:46 -06:00
disconnectClosureCb = objectCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'disconnect' , disconnectClosureCb ) ;
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled disconnect service subscriber' ) ;
Main . emitter . off ( 'disconnect' , disconnectClosureCb ) ;
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
2025-01-08 15:30:39 -06:00
let pingClosureCb = null ;
service . register ( "ping" , ( message : any ) = > {
if ( message . isSubscription ) {
pingClosureCb = objectCb . bind ( this , message ) ;
Main . emitter . on ( 'ping' , pingClosureCb ) ;
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled ping service subscriber' ) ;
Main . emitter . off ( 'ping' , pingClosureCb ) ;
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
2024-12-09 00:56:55 -06:00
Main . discoveryService = new DiscoveryService ( ) ;
Main . discoveryService . start ( ) ;
Main . tcpListenerService = new TcpListenerService ( ) ;
Main . webSocketListenerService = new WebSocketListenerService ( ) ;
2025-01-06 20:35:57 -06:00
Main . emitter = new EventEmitter ( ) ;
2024-12-09 00:56:55 -06:00
let playData : PlayMessage = null ;
let playClosureCb = null ;
const playCb = ( message : any , playMessage : PlayMessage ) = > {
playData = playMessage ;
message . respond ( { returnValue : true , value : { playData : playData } } ) ;
} ;
let pauseClosureCb : any = null ;
let resumeClosureCb : any = null ;
let stopClosureCb : any = null ;
let seekClosureCb = null ;
const seekCb = ( message : any , seekMessage : SeekMessage ) = > { message . respond ( { returnValue : true , value : seekMessage } ) ; } ;
let setVolumeClosureCb = null ;
const setVolumeCb = ( message : any , volumeMessage : SetVolumeMessage ) = > { message . respond ( { returnValue : true , value : volumeMessage } ) ; } ;
let setSpeedClosureCb = null ;
const setSpeedCb = ( message : any , speedMessage : SetSpeedMessage ) = > { message . respond ( { returnValue : true , value : speedMessage } ) ; } ;
// Note: When logging the `message` object, do NOT use JSON.stringify, you can log messages directly. Seems to be a circular reference causing errors...
// const playService = service.register("play", (message) => {
service . register ( "play" , ( message : any ) = > {
if ( message . isSubscription ) {
playClosureCb = playCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'play' , playClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true , playData : playData } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled play service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'play' , playClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
service . register ( "pause" , ( message : any ) = > {
if ( message . isSubscription ) {
pauseClosureCb = voidCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'pause' , pauseClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled pause service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'pause' , pauseClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
service . register ( "resume" , ( message : any ) = > {
if ( message . isSubscription ) {
resumeClosureCb = voidCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'resume' , resumeClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled resume service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'resume' , resumeClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
service . register ( "stop" , ( message : any ) = > {
playData = null ;
if ( message . isSubscription ) {
stopClosureCb = voidCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'stop' , stopClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled stop service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'stop' , stopClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
service . register ( "seek" , ( message : any ) = > {
if ( message . isSubscription ) {
seekClosureCb = seekCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'seek' , seekClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled seek service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'seek' , seekClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
service . register ( "setvolume" , ( message : any ) = > {
if ( message . isSubscription ) {
setVolumeClosureCb = setVolumeCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'setvolume' , setVolumeClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled setvolume service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'setvolume' , setVolumeClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
service . register ( "setspeed" , ( message : any ) = > {
if ( message . isSubscription ) {
setSpeedClosureCb = setSpeedCb . bind ( this , message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . on ( 'setspeed' , setSpeedClosureCb ) ;
2024-12-09 00:56:55 -06:00
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
Main . logger . info ( 'Canceled setspeed service subscriber' ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . off ( 'setspeed' , setSpeedClosureCb ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
const listeners = [ Main . tcpListenerService , Main . webSocketListenerService ] ;
listeners . forEach ( l = > {
l . emitter . on ( "play" , async ( message ) = > {
await NetworkService . proxyPlayIfRequired ( message ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . emit ( 'play' , message ) ;
2024-12-09 00:56:55 -06:00
2024-12-17 00:10:12 -06:00
const appId = 'com.futo.fcast.receiver' ;
service . call ( "luna://com.webos.applicationManager/launch" , {
'id' : appId ,
2024-12-18 20:43:47 -06:00
'params' : { timestamp : Date.now ( ) , playData : message }
2024-12-17 00:10:12 -06:00
} , ( response : any ) = > {
Main . logger . info ( ` Launch response: ${ JSON . stringify ( response ) } ` ) ;
Main . logger . info ( ` Relaunching FCast Receiver with args: ${ JSON . stringify ( message ) } ` ) ;
2024-12-09 00:56:55 -06:00
} ) ;
} ) ;
2025-01-06 20:35:57 -06:00
l . emitter . on ( "pause" , ( ) = > Main . emitter . emit ( 'pause' ) ) ;
l . emitter . on ( "resume" , ( ) = > Main . emitter . emit ( 'resume' ) ) ;
l . emitter . on ( "stop" , ( ) = > Main . emitter . emit ( 'stop' ) ) ;
l . emitter . on ( "seek" , ( message ) = > Main . emitter . emit ( 'seek' , message ) ) ;
l . emitter . on ( "setvolume" , ( message ) = > Main . emitter . emit ( 'setvolume' , message ) ) ;
l . emitter . on ( "setspeed" , ( message ) = > Main . emitter . emit ( 'setspeed' , message ) ) ;
l . emitter . on ( 'connect' , ( message ) = > Main . emitter . emit ( 'connect' , message ) ) ;
l . emitter . on ( 'disconnect' , ( message ) = > Main . emitter . emit ( 'disconnect' , message ) ) ;
2025-01-08 15:30:39 -06:00
l . emitter . on ( 'ping' , ( message ) = > Main . emitter . emit ( 'ping' , message ) ) ;
2024-12-09 00:56:55 -06:00
l . start ( ) ;
} ) ;
service . register ( "send_playback_error" , ( message : any ) = > {
listeners . forEach ( l = > {
const value : PlaybackErrorMessage = message . payload . error ;
l . send ( Opcode . PlaybackError , value ) ;
} ) ;
message . respond ( { returnValue : true , value : { success : true } } ) ;
} ) ;
service . register ( "send_playback_update" , ( message : any ) = > {
// Main.logger.info("In send_playback_update callback");
listeners . forEach ( l = > {
const value : PlaybackUpdateMessage = message . payload . update ;
l . send ( Opcode . PlaybackUpdate , value ) ;
} ) ;
message . respond ( { returnValue : true , value : { success : true } } ) ;
} ) ;
service . register ( "send_volume_update" , ( message : any ) = > {
listeners . forEach ( l = > {
const value : VolumeUpdateMessage = message . payload . update ;
l . send ( Opcode . VolumeUpdate , value ) ;
} ) ;
message . respond ( { returnValue : true , value : { success : true } } ) ;
} ) ;
}
catch ( err ) {
Main . logger . error ( "Error initializing service:" , err ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . emit ( 'toast' , { message : ` Error initializing service: ${ err } ` , icon : ToastIcon.ERROR } ) ;
2024-12-09 00:56:55 -06:00
}
}
}
export function getComputerName() {
return os . hostname ( ) ;
}
export async function errorHandler ( err : NodeJS.ErrnoException ) {
Main . logger . error ( "Application error:" , err ) ;
2025-01-06 20:35:57 -06:00
Main . emitter . emit ( 'toast' , { message : err , icon : ToastIcon.ERROR } ) ;
2024-12-09 00:56:55 -06:00
}