import * as ws from "ws"; import * as electron from "electron"; const WebSocketServer = ws.Server; interface standardResponse { status?: Number, message?: String, data?: any, type?: string, } export class wsapi { static clients: any; port: any = 26369 wss: any = null clients: any = [] private _win: any; constructor(win: any) { this._win = win; } createId() { // create random guid return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } public async InitWebSockets() { electron.ipcMain.on('wsapi-updatePlaybackState', (_event: any, arg: any) => { this.updatePlaybackState(arg); }) electron.ipcMain.on('wsapi-returnQueue', (_event: any, arg: any) => { this.returnQueue(JSON.parse(arg)); }); electron.ipcMain.on('wsapi-returnSearch', (_event: any, arg: any) => { console.log("SEARCH") this.returnSearch(JSON.parse(arg)); }); electron.ipcMain.on('wsapi-returnSearchLibrary', (_event: any, arg: any) => { this.returnSearchLibrary(JSON.parse(arg)); }); electron.ipcMain.on('wsapi-returnDynamic', (_event: any, arg: any, type: any) => { this.returnDynamic(JSON.parse(arg), type); }); electron.ipcMain.on('wsapi-returnMusicKitApi', (_event: any, arg: any, method: any) => { this.returnMusicKitApi(JSON.parse(arg), method); }); electron.ipcMain.on('wsapi-returnLyrics', (_event: any, arg: any) => { this.returnLyrics(JSON.parse(arg)); }); electron.ipcMain.on('wsapi-returnvolumeMax', (_event: any, arg: any) => { this.returnmaxVolume(JSON.parse(arg)); }); this.wss = new WebSocketServer({ port: this.port, perMessageDeflate: { zlibDeflateOptions: { // See zlib defaults. chunkSize: 1024, memLevel: 7, level: 3 }, zlibInflateOptions: { chunkSize: 10 * 1024 }, // Other options settable: clientNoContextTakeover: true, // Defaults to negotiated value. serverNoContextTakeover: true, // Defaults to negotiated value. serverMaxWindowBits: 10, // Defaults to negotiated value. // Below options specified as default values. concurrencyLimit: 10, // Limits zlib concurrency for perf. threshold: 1024 // Size (in bytes) below which messages // should not be compressed if context takeover is disabled. } }) console.log(`WebSocketServer started on port: ${this.port}`); const defaultResponse: standardResponse = {status: 0, data: {}, message: "OK", type: "generic"}; this.wss.on('connection', (ws: any) => { ws.id = this.createId(); console.log(`Client ${ws.id} connected`) this.clients.push(ws); ws.on('message', function incoming(_message: any) { }); // ws on message ws.on('message', (message: any) => { let data = JSON.parse(message); let response: standardResponse = {status: 0, data: {}, message: "OK", type: "generic"}; if (data.action) { data.action.toLowerCase(); } switch (data.action) { default: response.message = "Action not found"; break; case "identify": response.message = "Thanks for identifying!" response.data = { id: ws.id } ws.identity = { name: data.name, author: data.author, description: data.description, version: data.version } break; case "play-next": this._win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`); response.message = "Play Next"; break; case "play-later": this._win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`); response.message = "Play Later"; break; case "quick-play": this._win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`); response.message = "Quick Play"; break; case "get-lyrics": this._win.webContents.executeJavaScript(`wsapi.getLyrics()`); break; case "shuffle": this._win.webContents.executeJavaScript(`wsapi.toggleShuffle()`); break; case "set-shuffle": if (data.shuffle == true) { this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`); } else { this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`); } break; case "repeat": this._win.webContents.executeJavaScript(`wsapi.toggleRepeat()`); break; case "seek": this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`); response.message = "Seek"; break; case "pause": this._win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`); response.message = "Paused"; break; case "play": this._win.webContents.executeJavaScript(`MusicKit.getInstance().play()`); response.message = "Playing"; break; case "stop": this._win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`); response.message = "Stopped"; break; case "volumeMax": this._win.webContents.executeJavaScript(`wsapi.getmaxVolume()`); response.message = "maxVolume"; break; case "volume": this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`); response.message = "Volume"; break; case "mute": this._win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`); response.message = "Muted"; break; case "unmute": this._win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`); response.message = "Unmuted"; break; case "next": this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) { try { app.prevButtonBackIndicator = false; } catch (e) { } MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}`); response.message = "Next"; break; case "previous": this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`); response.message = "Previous"; break; case "musickit-api": this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`); break; case "musickit-library-api": break; case "set-autoplay": this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`); break; case "queue-move": this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`); break; case "get-queue": this._win.webContents.executeJavaScript(`wsapi.getQueue()`); break; case "search": if (!data.limit) { data.limit = 10; } this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`); break; case "library-search": if (!data.limit) { data.limit = 10; } this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`); break; case "show-window": this._win.show() break; case "hide-window": this._win.hide() break; case "play-mediaitem": this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`); response.message = "Playing track"; break; case "get-status": response.data = { isAuthorized: true }; response.message = "Status"; break; case "get-currentmediaitem": this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`); break; case "quit": electron.app.quit(); break; } ws.send(JSON.stringify(response)); }); ws.on('close', () => { // remove client from list this.clients.splice(wsapi.clients.indexOf(ws), 1); console.log(`Client ${ws.id} disconnected`); }); ws.send(JSON.stringify(defaultResponse)); }); } sendToClient(_id: any) { // replace the clients.forEach with a filter to find the client that requested } updatePlaybackState(attr: any) { const response: standardResponse = {status: 0, data: attr, message: "OK", type: "playbackStateUpdate"}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnMusicKitApi(results: any, method: any) { const response: standardResponse = {status: 0, data: results, message: "OK", type: `musickitapi.${method}`}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnDynamic(results: any, type: any) { const response: standardResponse = {status: 0, data: results, message: "OK", type: type}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnLyrics(results: any) { const response: standardResponse = {status: 0, data: results, message: "OK", type: "lyrics"}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnSearch(results: any) { const response: standardResponse = {status: 0, data: results, message: "OK", type: "searchResults"}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnSearchLibrary(results: any) { const response: standardResponse = {status: 0, data: results, message: "OK", type: "searchResultsLibrary"}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnQueue(queue: any) { const response: standardResponse = {status: 0, data: queue, message: "OK", type: "queue"}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } returnmaxVolume(vol: any) { const response: standardResponse = {status: 0, data: vol, message: "OK", type: "maxVolume"}; this.clients.forEach(function each(client: any) { client.send(JSON.stringify(response)); }); } }