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');
2025-02-13 11:21:49 -06:00
import { Opcode , PlayMessage , PlaybackErrorMessage , PlaybackUpdateMessage , SeekMessage , SetSpeedMessage , SetVolumeMessage , VolumeUpdateMessage } from 'common/Packets' ;
2024-12-09 00:56:55 -06:00
import { DiscoveryService } from 'common/DiscoveryService' ;
import { TcpListenerService } from 'common/TcpListenerService' ;
import { WebSocketListenerService } from 'common/WebSocketListenerService' ;
import { NetworkService } from 'common/NetworkService' ;
2025-05-09 10:44:26 -05:00
import { ConnectionMonitor } from 'common/ConnectionMonitor' ;
import { Logger , LoggerType } from 'common/Logger' ;
2024-12-09 00:56:55 -06:00
import * as os from 'os' ;
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' ;
2025-05-09 10:44:26 -05:00
const logger = new Logger ( 'Main' , LoggerType . BACKEND ) ;
2024-12-09 00:56:55 -06:00
export class Main {
static tcpListenerService : TcpListenerService ;
static webSocketListenerService : WebSocketListenerService ;
static discoveryService : DiscoveryService ;
2025-05-09 10:44:26 -05:00
static connectionMonitor : ConnectionMonitor ;
2025-01-06 20:35:57 -06:00
static emitter : EventEmitter ;
2024-12-09 00:56:55 -06:00
static {
try {
2025-05-09 10:44:26 -05:00
logger . info ( ` OS: ${ process . platform } ${ process . arch } ` ) ;
2024-12-09 00:56:55 -06:00
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
2025-01-08 23:57:30 -06:00
registerService ( service , 'toast' , ( message : any ) = > { return objectCb . bind ( this , message ) } ) ;
2025-01-06 20:35:57 -06:00
2025-05-09 10:44:26 -05:00
// getDeviceInfo and network-changed handled in frontend
service . register ( "get_sessions" , ( message : any ) = > {
2024-12-09 00:56:55 -06:00
message . respond ( {
returnValue : true ,
2025-05-09 10:44:26 -05:00
value : [ ] . concat ( Main . tcpListenerService . getSenders ( ) , Main . webSocketListenerService . getSessions ( ) )
2024-12-09 00:56:55 -06:00
} ) ;
} ) ;
2025-01-08 23:57:30 -06:00
registerService ( service , 'connect' , ( message : any ) = > { return objectCb . bind ( this , message ) } ) ;
registerService ( service , 'disconnect' , ( message : any ) = > { return objectCb . bind ( this , message ) } ) ;
2025-01-08 15:30:39 -06:00
2025-05-09 10:44:26 -05:00
Main . connectionMonitor = new ConnectionMonitor ( ) ;
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 stopClosureCb : any = null ;
const seekCb = ( message : any , seekMessage : SeekMessage ) = > { message . respond ( { returnValue : true , value : seekMessage } ) ; } ;
const setVolumeCb = ( message : any , volumeMessage : SetVolumeMessage ) = > { message . respond ( { returnValue : true , value : volumeMessage } ) ; } ;
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...
2025-01-08 23:57:30 -06:00
service . register ( 'play' , ( message : any ) = > {
2024-12-09 00:56:55 -06:00
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 ) = > {
2025-05-09 10:44:26 -05:00
logger . info ( 'Canceled play service subscriber' ) ;
2025-02-19 12:46:09 -06:00
Main . emitter . removeAllListeners ( 'play' ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
2025-01-08 23:57:30 -06:00
registerService ( service , 'pause' , ( message : any ) = > { return voidCb . bind ( this , message ) } ) ;
registerService ( service , 'resume' , ( message : any ) = > { return voidCb . bind ( this , message ) } ) ;
2024-12-09 00:56:55 -06:00
2025-01-08 23:57:30 -06:00
service . register ( 'stop' , ( message : any ) = > {
2024-12-09 00:56:55 -06:00
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 ) = > {
2025-05-09 10:44:26 -05:00
logger . info ( 'Canceled stop service subscriber' ) ;
2025-02-19 12:46:09 -06:00
Main . emitter . removeAllListeners ( 'stop' ) ;
2024-12-09 00:56:55 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
2025-01-08 23:57:30 -06:00
registerService ( service , 'seek' , ( message : any ) = > { return seekCb . bind ( this , message ) } ) ;
registerService ( service , 'setvolume' , ( message : any ) = > { return setVolumeCb . bind ( this , message ) } ) ;
registerService ( service , 'setspeed' , ( message : any ) = > { return setSpeedCb . bind ( this , message ) } ) ;
2024-12-09 00:56:55 -06:00
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 ) = > {
2025-05-09 10:44:26 -05:00
logger . info ( ` Launch response: ${ JSON . stringify ( response ) } ` ) ;
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 ) ) ;
2025-05-09 10:44:26 -05:00
l . emitter . on ( 'connect' , ( message ) = > {
ConnectionMonitor . onConnect ( l , message , l instanceof WebSocketListenerService , ( ) = > {
Main . emitter . emit ( 'connect' , message ) ;
} ) ;
} ) ;
l . emitter . on ( 'disconnect' , ( message ) = > {
ConnectionMonitor . onDisconnect ( l , message , l instanceof WebSocketListenerService , ( ) = > {
Main . emitter . emit ( 'disconnect' , message ) ;
} ) ;
} ) ;
l . emitter . on ( 'ping' , ( message ) = > {
ConnectionMonitor . onPingPong ( message , l instanceof WebSocketListenerService ) ;
} ) ;
l . emitter . on ( 'pong' , ( message ) = > {
ConnectionMonitor . onPingPong ( message , l instanceof WebSocketListenerService ) ;
} ) ;
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 ) = > {
2025-05-09 10:44:26 -05:00
// logger.info("In send_playback_update callback");
2024-12-09 00:56:55 -06:00
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 ) {
2025-05-09 10:44:26 -05:00
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 ( ) ;
}
2025-05-09 10:44:26 -05:00
export async function errorHandler ( error : Error ) {
logger . error ( error ) ;
logger . shutdown ( ) ;
logger . error ( "Application error:" , error ) ;
Main . emitter . emit ( 'toast' , { message : error , icon : ToastIcon.ERROR } ) ;
2024-12-09 00:56:55 -06:00
}
2025-01-08 23:57:30 -06:00
function registerService ( service : Service , method : string , callback : ( message : any ) = > any ) {
let callbackRef = null ;
service . register ( method , ( message : any ) = > {
if ( message . isSubscription ) {
callbackRef = callback ( message ) ;
Main . emitter . on ( method , callbackRef ) ;
}
message . respond ( { returnValue : true , value : { subscribed : true } } ) ;
} ,
( message : any ) = > {
2025-05-09 10:44:26 -05:00
logger . info ( ` Canceled ${ method } service subscriber ` ) ;
2025-02-19 12:46:09 -06:00
Main . emitter . removeAllListeners ( method ) ;
2025-01-08 23:57:30 -06:00
message . respond ( { returnValue : true , value : message.payload } ) ;
} ) ;
}