Small bugfix, security patch

This commit is contained in:
2025-06-25 10:51:16 +02:00
parent b0a1f9a538
commit f0c538126d
6 changed files with 3225 additions and 308 deletions

View File

@@ -125,7 +125,7 @@ class NotificationHandler {
}, 1000 * this.reconnectRetryCount ); }, 1000 * this.reconnectRetryCount );
} }
}; };
} else if ( res.status === 403 || res.status === 401 || res.status === 404 ) { } else if ( res.status === 403 || res.status === 401 || res.status === 404 || res.status === 402 ) {
document.dispatchEvent( new Event( 'musicplayer:autherror' ) ); document.dispatchEvent( new Event( 'musicplayer:autherror' ) );
reject( 'ERR_UNAUTHORIZED' ); reject( 'ERR_UNAUTHORIZED' );
} else { } else {

View File

@@ -90,7 +90,7 @@
router.push( '/get' ); router.push( '/get' );
} }
} ); } );
} else if ( res.status === 404 ) { } else if ( res.status === 402 ) {
userStore.setSubscriptionStatus( false ); userStore.setSubscriptionStatus( false );
router.push( '/get' ); router.push( '/get' );
sessionStorage.setItem( 'getRedirectionReason', 'notOwned' ); sessionStorage.setItem( 'getRedirectionReason', 'notOwned' );

702
backend/eslint.config.mjs Normal file
View File

@@ -0,0 +1,702 @@
import vue from 'eslint-plugin-vue';
import eslint from '@eslint/js';
import globals from 'globals';
import typescript from '@typescript-eslint/eslint-plugin';
import stylistic from '@stylistic/eslint-plugin';
import tseslint from 'typescript-eslint';
const style = {
'plugins': {
'@stylistic': stylistic,
'@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
},
'files': [
'**/*.ts',
'**/*.js',
'**/*.mjs',
'**/*.cjs',
'**/*.tsx',
'**/*.jsx'
],
'rules': {
// Formatting
'@stylistic/array-bracket-newline': [
'error',
{
'multiline': true,
'minItems': 2
}
],
'@stylistic/array-bracket-spacing': [
'error',
'always'
],
'@stylistic/array-element-newline': [
'error',
{
'multiline': true,
'minItems': 2
}
],
'@stylistic/arrow-parens': [
'error',
'as-needed'
],
'@stylistic/arrow-spacing': [
'error',
{
'before': true,
'after': true
}
],
'@stylistic/block-spacing': [
'error',
'always'
],
'@stylistic/brace-style': [
'error',
'1tbs'
],
'@stylistic/comma-spacing': [
'error',
{
'before': false,
'after': true
}
],
'@stylistic/comma-style': [
'error',
'last'
],
'@stylistic/dot-location': [
'error',
'property'
],
'@stylistic/eol-last': [
'error',
'always'
],
'@stylistic/function-call-spacing': [
'error',
'never'
],
'@stylistic/function-paren-newline': [
'error',
'multiline'
],
'@stylistic/function-call-argument-newline': [
'error',
'consistent'
],
'@stylistic/implicit-arrow-linebreak': [
'error',
'beside'
],
'@stylistic/indent': [
'error',
4
],
'@stylistic/key-spacing': [
'error',
{
'beforeColon': false,
'afterColon': true
}
],
'@stylistic/keyword-spacing': [
'error',
{
'before': true,
'after': true
}
],
'@stylistic/lines-between-class-members': [
'error',
'always'
],
'@stylistic/max-len': [
'warn',
{
'code': 90,
'comments': 100,
'ignoreComments': false,
'ignoreUrls': true,
'ignoreStrings': false
}
],
'@stylistic/new-parens': [
'error',
'always'
],
'@stylistic/newline-per-chained-call': [ 'error' ],
'@stylistic/no-extra-parens': [
'error',
'all',
{
'nestedBinaryExpressions': false,
'ternaryOperandBinaryExpressions': false,
'ignoreJSX': 'multi-line',
'nestedConditionalExpressions': false
}
],
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-floating-decimal': 'error',
'@stylistic/no-mixed-operators': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-multi-spaces': 'error',
'@stylistic/no-multiple-empty-lines': [
'error',
{
'max': 3,
'maxEOF': 2
}
],
'@stylistic/no-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/no-whitespace-before-property': 'error',
'@stylistic/object-curly-newline': [
'error',
{
'multiline': true,
'minProperties': 1
}
],
'@stylistic/object-curly-spacing': [
'error',
'always'
],
'@stylistic/object-property-newline': 'error',
'@stylistic/operator-linebreak': [
'error',
'before'
],
'@stylistic/one-var-declaration-per-line': 'error',
'@stylistic/padded-blocks': [
'error',
{
'blocks': 'never',
'classes': 'always',
'switches': 'never',
}
],
// Padding lines. The most in-depth part of this config
'@stylistic/padding-line-between-statements': [
'error',
// Variables, Constants
{
'blankLine': 'never',
'prev': 'var',
'next': 'var'
},
{
'blankLine': 'never',
'prev': 'let',
'next': 'let'
},
{
'blankLine': 'never',
'prev': 'const',
'next': 'const'
},
{
'blankLine': 'always',
'prev': 'var',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'let',
'return',
'switch',
'throw',
'try',
'var',
'with'
]
},
{
'blankLine': 'always',
'prev': 'let',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
{
'blankLine': 'always',
'prev': 'const',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
// Import
{
'blankLine': 'never',
'prev': 'import',
'next': 'import'
},
{
'blankLine': 'never',
'prev': 'cjs-import',
'next': 'cjs-import'
},
{
'blankLine': 'always',
'prev': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
],
'next': 'cjs-import'
},
{
'blankLine': 'always',
'prev': 'cjs-import',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
{
'blankLine': 'always',
'prev': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
],
'next': 'import'
},
{
'blankLine': 'always',
'prev': 'import',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
// If
{
'blankLine': 'always',
'prev': '*',
'next': 'if'
},
{
'blankLine': 'always',
'prev': 'if',
'next': '*'
},
// For
{
'blankLine': 'always',
'prev': '*',
'next': 'for'
},
{
'blankLine': 'always',
'prev': 'for',
'next': '*'
},
// While
{
'blankLine': 'always',
'prev': '*',
'next': 'while'
},
{
'blankLine': 'always',
'prev': 'while',
'next': '*'
},
// Functions
{
'blankLine': 'always',
'prev': '*',
'next': 'function'
},
{
'blankLine': 'always',
'prev': 'function',
'next': '*'
},
// Block Statements
{
'blankLine': 'always',
'prev': '*',
'next': 'block-like'
},
{
'blankLine': 'always',
'prev': 'block-like',
'next': '*'
},
// Switch
{
'blankLine': 'always',
'prev': '*',
'next': 'switch'
},
{
'blankLine': 'always',
'prev': 'switch',
'next': '*'
},
// Try-Catch
{
'blankLine': 'always',
'prev': '*',
'next': 'try'
},
{
'blankLine': 'always',
'prev': 'try',
'next': '*'
},
// Throw
{
'blankLine': 'always',
'prev': '*',
'next': 'throw'
},
{
'blankLine': 'always',
'prev': 'throw',
'next': '*'
},
// Return
{
'blankLine': 'never',
'prev': 'return',
'next': '*'
},
{
'blankLine': 'always',
'prev': '*',
'next': 'return'
},
// Export
{
'blankLine': 'always',
'prev': '*',
'next': 'export'
},
{
'blankLine': 'always',
'prev': 'export',
'next': '*'
},
{
'blankLine': 'always',
'prev': '*',
'next': 'cjs-export'
},
{
'blankLine': 'always',
'prev': 'cjs-export',
'next': '*'
},
// Classes
{
'blankLine': 'always',
'prev': '*',
'next': 'class'
},
{
'blankLine': 'always',
'prev': 'class',
'next': '*'
},
],
'@stylistic/quote-props': [
'error',
'always'
],
'@stylistic/quotes': [
'error',
'single'
],
'@stylistic/rest-spread-spacing': [
'error',
'never'
],
'@stylistic/semi': [
'error',
'always'
],
'@stylistic/semi-spacing': [
'error',
{
'before': false,
'after': true
}
],
'@stylistic/semi-style': [
'error',
'last'
],
'@stylistic/space-before-blocks': [
'error',
'always'
],
'@stylistic/space-before-function-paren': [
'error',
'always'
],
'@stylistic/space-in-parens': [
'error',
'always'
],
'@stylistic/space-infix-ops': [
'error',
{
'int32Hint': false
}
],
'@stylistic/space-unary-ops': 'error',
'@stylistic/spaced-comment': [
'error',
'always'
],
'@stylistic/template-curly-spacing': [
'error',
'always'
],
'@stylistic/switch-colon-spacing': 'error',
'@stylistic/wrap-iife': [
'error',
'inside'
],
'@stylistic/wrap-regex': 'error',
'@stylistic/ts/type-annotation-spacing': 'error',
}
};
/** @type {import('eslint').Linter.Config} */
export default tseslint.config(
// Base JavaScript rules
eslint.configs.recommended,
tseslint.configs.recommended,
style,
// Vue support (including TS and JSX inside SFCs)
{
'files': [ '**/*.vue' ],
'languageOptions': {
'sourceType': 'module',
'ecmaVersion': 'latest',
'globals': globals.browser,
'parserOptions': {
'parser': tseslint.parser,
},
},
'plugins': {
'vue': vue,
'@stylistic': stylistic,
'@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
'@typescript-eslint': typescript,
},
'extends': [
eslint.configs.recommended,
...vue.configs['flat/recommended']
],
'rules': {
...typescript.configs.recommended.rules,
...style.rules,
// Vue specific rules
'@stylistic/indent': 'off',
'vue/html-indent': [
'error',
4
],
'vue/html-comment-indent': [
'error',
4
],
'vue/script-indent': [
'error',
4,
{
'baseIndent': 1,
'switchCase': 1
}
],
'vue/html-self-closing': [
'error',
{
'html': {
'void': 'never',
'normal': 'never',
'component': 'always'
},
'svg': 'always',
'math': 'never'
}
],
'vue/max-attributes-per-line': [
'error',
{
'singleline': 3,
'multiline': 1,
}
],
},
},
);

2110
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,12 @@
}, },
"homepage": "https://github.com/simplePCBuilding/MusicPlayerV2#readme", "homepage": "https://github.com/simplePCBuilding/MusicPlayerV2#readme",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.29.0",
"@stylistic/eslint-plugin": "^5.0.0",
"@types/express-session": "^1.18.0", "@types/express-session": "^1.18.0",
"typescript": "^5.4.5" "eslint-plugin-vue": "^10.2.0",
"typescript": "^5.4.5",
"typescript-eslint": "^8.35.0"
}, },
"dependencies": { "dependencies": {
"@types/body-parser": "^1.19.5", "@types/body-parser": "^1.19.5",

View File

@@ -5,34 +5,54 @@ import jwt from 'jsonwebtoken';
import cors from 'cors'; import cors from 'cors';
import account from './account'; import account from './account';
import sdk from 'oauth-janishutz-client-server'; import sdk from 'oauth-janishutz-client-server';
import { createServer } from 'node:http'; import {
import { Server } from 'socket.io'; createServer
} from 'node:http';
import {
Server
} from 'socket.io';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import type { Room, Song } from './definitions'; import type {
Room, Song
} from './definitions';
import storeSDK from 'store.janishutz.com-sdk'; import storeSDK from 'store.janishutz.com-sdk';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
const isFossVersion = true; const isFossVersion = true;
declare let __dirname: string | undefined declare let __dirname: string | undefined;
if ( typeof( __dirname ) === 'undefined' ) {
if ( typeof __dirname === 'undefined' ) {
__dirname = path.resolve( path.dirname( '' ) ); __dirname = path.resolve( path.dirname( '' ) );
} }
// TODO: Change config file, as well as in main.ts, index.html, oauth, if deploying there // TODO: Change config file, as well as in main.ts, index.html, oauth, if deploying there
// const sdkConfig = JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/sdk.config.testing.json' ) ) ); // const sdkConfig = JSON.parse( fs.readFileSync( path.join(
const sdkConfig = JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/sdk.config.secret.json' ) ) ); // __dirname,
// '/config/sdk.config.testing.json'
// ) ).toString() );
const sdkConfig = JSON.parse( fs.readFileSync( path.join(
__dirname,
'/config/sdk.config.secret.json'
) ).toString() );
const run = () => { const run = () => {
let app = express(); const app = express();
app.use( cors( { app.use( cors( {
credentials: true, 'credentials': true,
origin: true 'origin': true
} ) ); } ) );
if ( !isFossVersion ) { if ( !isFossVersion ) {
// storeSDK.configure( JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/store-sdk.config.testing.json' ) ) ) ); // storeSDK.configure( JSON.parse( fs.readFileSync( path.join(
storeSDK.configure( JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/store-sdk.config.secret.json' ) ) ) ); // __dirname,
// '/config/store-sdk.config.testing.json'
// ) ).toString() ) );
storeSDK.configure( JSON.parse( fs.readFileSync( path.join(
__dirname,
'/config/store-sdk.config.secret.json'
) ).toString() ) );
} }
const httpServer = createServer( app ); const httpServer = createServer( app );
@@ -43,18 +63,19 @@ const run = () => {
return new Promise( ( resolve, reject ) => { return new Promise( ( resolve, reject ) => {
account.checkUser( uid ).then( stat => { account.checkUser( uid ).then( stat => {
resolve( stat ); resolve( stat );
} ).catch( e => { } )
reject( e ); .catch( e => {
} ); reject( e );
} );
} ); } );
}, }, ( uid: string, email: string, username: string ) => {
( uid: string, email: string, username: string ) => {
return new Promise( ( resolve, reject ) => { return new Promise( ( resolve, reject ) => {
account.createUser( uid, username, email ).then( stat => { account.createUser( uid, username, email ).then( stat => {
resolve( stat ); resolve( stat );
} ).catch( e => { } )
reject( e ); .catch( e => {
} ); reject( e );
} );
} ); } );
}, sdkConfig ); }, sdkConfig );
} }
@@ -65,89 +86,118 @@ const run = () => {
} }
const socketData: SocketData = {}; const socketData: SocketData = {};
const io = new Server( httpServer, { const io = new Server( httpServer, {
cors: { 'cors': {
origin: true, 'origin': true,
credentials: true, 'credentials': true,
} }
} ); } );
io.on( 'connection', ( socket ) => { io.on( 'connection', socket => {
socket.on( 'create-room', ( room: { name: string, token: string }, cb: ( res: { status: boolean, msg: string } ) => void ) => { socket.on( 'create-room', ( room: {
'name': string,
'token': string
}, cb: ( res: {
'status': boolean,
'msg': string
} ) => void ) => {
if ( socketData[ room.name ] ) { if ( socketData[ room.name ] ) {
if ( room.token === socketData[ room.name ].roomToken ) { if ( room.token === socketData[ room.name ].roomToken ) {
socket.join( room.name ); socket.join( room.name );
cb( { cb( {
status: true, 'status': true,
msg: 'ADDED_TO_ROOM' 'msg': 'ADDED_TO_ROOM'
} ); } );
} else { } else {
cb( { cb( {
status: false, 'status': false,
msg: 'ERR_TOKEN_INVALID' 'msg': 'ERR_TOKEN_INVALID'
} ); } );
} }
} else { } else {
cb( { cb( {
status: false, 'status': false,
msg: 'ERR_NAME_INVALID' 'msg': 'ERR_NAME_INVALID'
} ); } );
} }
} ); } );
socket.on( 'delete-room', ( room: { name: string, token: string }, cb: ( res: { status: boolean, msg: string } ) => void ) => { socket.on( 'delete-room', ( room: {
'name': string,
'token': string
}, cb: ( res: {
'status': boolean,
'msg': string
} ) => void ) => {
if ( socketData[ room.name ] ) { if ( socketData[ room.name ] ) {
if ( room.token === socketData[ room.name ].roomToken ) { if ( room.token === socketData[ room.name ].roomToken ) {
socket.leave( room.name ); socket.leave( room.name );
socket.to( room.name ).emit( 'delete-share', room.name ); socket.to( room.name ).emit( 'delete-share', room.name );
socketData[ room.name ] = undefined; socketData[ room.name ] = undefined;
cb( { cb( {
status: true, 'status': true,
msg: 'ROOM_DELETED' 'msg': 'ROOM_DELETED'
} ); } );
} else { } else {
cb( { cb( {
status: false, 'status': false,
msg: 'ERR_TOKEN_INVALID' 'msg': 'ERR_TOKEN_INVALID'
} ); } );
} }
} else { } else {
cb( { cb( {
status: false, 'status': false,
msg: 'ERR_NAME_INVALID' 'msg': 'ERR_NAME_INVALID'
} ); } );
} }
} ); } );
socket.on( 'join-room', ( room: string, cb: ( res: { status: boolean, msg: string, data?: { playbackStatus: boolean, playbackStart: number, playlist: Song[], playlistIndex: number, useAntiTamper: boolean } } ) => void ) => { socket.on( 'join-room', ( room: string, cb: ( res: {
'status': boolean,
'msg': string,
'data'?: {
'playbackStatus': boolean,
'playbackStart': number,
'playlist': Song[],
'playlistIndex': number,
'useAntiTamper': boolean
}
} ) => void ) => {
if ( socketData[ room ] ) { if ( socketData[ room ] ) {
socket.join( room ); socket.join( room );
cb( { cb( {
data: { 'data': {
playbackStart: socketData[ room ].playbackStart, 'playbackStart': socketData[ room ].playbackStart,
playbackStatus: socketData[ room ].playbackStatus, 'playbackStatus': socketData[ room ].playbackStatus,
playlist: socketData[ room ].playlist, 'playlist': socketData[ room ].playlist,
playlistIndex: socketData[ room ].playlistIndex, 'playlistIndex': socketData[ room ].playlistIndex,
useAntiTamper: socketData[ room ].useAntiTamper, 'useAntiTamper': socketData[ room ].useAntiTamper,
}, },
msg: 'STATUS_OK', 'msg': 'STATUS_OK',
status: true, 'status': true,
} ) } );
} else { } else {
cb( { cb( {
msg: 'ERR_NO_ROOM_WITH_THIS_ID', 'msg': 'ERR_NO_ROOM_WITH_THIS_ID',
status: false, 'status': false,
} ); } );
socket.disconnect(); socket.disconnect();
} }
} ); } );
socket.on( 'tampering', ( data: { msg: string, roomName: string } ) => { socket.on( 'tampering', ( data: {
'msg': string,
'roomName': string
} ) => {
if ( data.roomName ) { if ( data.roomName ) {
socket.to( data.roomName ).emit( 'tampering-msg', data.msg ); socket.to( data.roomName ).emit( 'tampering-msg', data.msg );
} }
} ) } );
socket.on( 'playlist-update', ( data: { roomName: string, roomToken: string, data: Song[] } ) => { socket.on( 'playlist-update', ( data: {
'roomName': string,
'roomToken': string,
'data': Song[]
} ) => {
if ( socketData[ data.roomName ] ) { if ( socketData[ data.roomName ] ) {
if ( socketData[ data.roomName ].roomToken === data.roomToken ) { if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
if ( socketData[ data.roomName ].playlist !== data.data ) { if ( socketData[ data.roomName ].playlist !== data.data ) {
@@ -158,7 +208,11 @@ const run = () => {
} }
} ); } );
socket.on( 'playback-update', ( data: { roomName: string, roomToken: string, data: boolean } ) => { socket.on( 'playback-update', ( data: {
'roomName': string,
'roomToken': string,
'data': boolean
} ) => {
if ( socketData[ data.roomName ] ) { if ( socketData[ data.roomName ] ) {
if ( socketData[ data.roomName ].roomToken === data.roomToken ) { if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
socketData[ data.roomName ].playbackStatus = data.data; socketData[ data.roomName ].playbackStatus = data.data;
@@ -167,7 +221,11 @@ const run = () => {
} }
} ); } );
socket.on( 'playlist-index-update', ( data: { roomName: string, roomToken: string, data: number } ) => { socket.on( 'playlist-index-update', ( data: {
'roomName': string,
'roomToken': string,
'data': number
} ) => {
if ( socketData[ data.roomName ] ) { if ( socketData[ data.roomName ] ) {
if ( socketData[ data.roomName ].roomToken === data.roomToken ) { if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
socketData[ data.roomName ].playlistIndex = data.data; socketData[ data.roomName ].playlistIndex = data.data;
@@ -176,7 +234,11 @@ const run = () => {
} }
} ); } );
socket.on( 'playback-start-update', ( data: { roomName: string, roomToken: string, data: number } ) => { socket.on( 'playback-start-update', ( data: {
'roomName': string,
'roomToken': string,
'data': number
} ) => {
if ( socketData[ data.roomName ] ) { if ( socketData[ data.roomName ] ) {
if ( socketData[ data.roomName ].roomToken === data.roomToken ) { if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
socketData[ data.roomName ].playbackStart = data.data; socketData[ data.roomName ].playbackStart = data.data;
@@ -196,8 +258,8 @@ const run = () => {
} }
interface SocketClient { interface SocketClient {
response: express.Response; 'response': express.Response;
room: string; 'room': string;
} }
interface ClientReferenceList { interface ClientReferenceList {
@@ -211,240 +273,373 @@ const run = () => {
const connectedClients: SocketClientList = {}; const connectedClients: SocketClientList = {};
const clientReference: ClientReferenceList = {}; const clientReference: ClientReferenceList = {};
app.get( '/socket/connection', ( request: express.Request, response: express.Response ) => { app.get(
if ( request.query.room ) { '/socket/connection',
if ( socketData[ String( request.query.room ) ] ) { ( request: express.Request, response: express.Response ) => {
response.writeHead( 200, { if ( request.query.room ) {
'Content-Type': 'text/event-stream', if ( socketData[ String( request.query.room ) ] ) {
'Cache-Control': 'no-cache', response.writeHead( 200, {
'Connection': 'keep-alive', 'Content-Type': 'text/event-stream',
} ); 'Cache-Control': 'no-cache',
response.status( 200 ); 'Connection': 'keep-alive',
response.flushHeaders(); } );
response.write( `data: ${ JSON.stringify( { 'type': 'basics', 'data': socketData[ String( request.query.room ) ] } ) }\n\n` ); response.status( 200 );
const sid = sdk.getSessionID( request ); response.flushHeaders();
if ( sdk.checkAuth( request ) ) { response.write( `data: ${ JSON.stringify( {
importantClients[ sid ] = { 'response': response, 'room': String( request.query.room ) }; 'type': 'basics',
} 'data': socketData[ String( request.query.room ) ]
connectedClients[ sid ] = { 'response': response, 'room': String( request.query.room ) }; } ) }\n\n` );
if ( !clientReference[ String( request.query.room ) ] ) { const sid = sdk.getSessionID( request );
clientReference[ String( request.query.room ) ] = [];
}
if ( !clientReference[ String( request.query.room ) ].includes( sid ) ) {
clientReference[ String( request.query.room ) ].push( sid );
}
request.on( 'close', () => {
try {
importantClients[ sid ] = undefined;
} catch ( e ) { /* empty */ }
const cl = clientReference[ String( request.query.room ) ];
for ( let c in cl ) {
if ( cl[ c ] === sid ) {
cl.splice( parseInt( c ), 1 );
break;
}
}
connectedClients[ sid ] = undefined;
} );
} else {
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
}
} else {
response.status( 404 ).send( 'ERR_NO_ROOM_SPECIFIED' );
}
} );
app.get( '/socket/getData', ( request: express.Request, response: express.Response ) => { if ( sdk.checkAuth( request ) ) {
if ( request.query.room ) { importantClients[ sid ] = {
response.send( socketData[ String( request.query.room ) ] ); 'response': response,
} else { 'room': String( request.query.room )
response.status( 400 ).send( 'ERR_NO_ROOM_SPECIFIED' ); };
}
} );
app.get( '/socket/joinRoom', ( request: express.Request, response: express.Response ) => {
if ( request.query.room ) {
if ( socketData[ String( request.query.room ) ] ) {
response.send( 'ok' );
} else {
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
}
} else {
response.status( 404 ).send( 'ERR_NO_ROOM_SPECIFIED' );
}
} );
app.post( '/socket/update', bodyParser.json(), ( request: express.Request, response: express.Response ) => {
if ( socketData[ request.body.roomName ] ) {
if ( request.body.event === 'tampering' ) {
const clients = clientReference[ request.body.roomName ];
for ( let client in clients ) {
if ( importantClients[ clients[ client ] ] ) {
importantClients[ clients[ client ] ].response.write( 'data: ' + JSON.stringify( { 'type': 'tampering-msg', 'data': true } ) + '\n\n' );
}
}
response.send( 'ok' );
} else {
if ( socketData[ request.body.roomName ].roomToken === request.body.roomToken ) {
let send = false;
let update = '';
if ( request.body.event === 'playback-start-update' ) {
send = true;
update = 'playback-start';
socketData[ request.body.roomName ].playbackStart = request.body.data;
} else if ( request.body.event === 'playback-update' ) {
send = true;
update = 'playback';
socketData[ request.body.roomName ].playbackStatus = request.body.data;
} else if ( request.body.event === 'playlist-update' ) {
send = true;
update = 'playlist';
socketData[ request.body.roomName ].playlist = request.body.data;
} else if ( request.body.event === 'playlist-index-update' ) {
send = true;
update = 'playlist-index';
socketData[ request.body.roomName ].playlistIndex = request.body.data;
} }
if ( send ) { connectedClients[ sid ] = {
const clients = clientReference[ request.body.roomName ]; 'response': response,
for ( let client in clients ) { 'room': String( request.query.room )
if ( connectedClients[ clients[ client ] ] ) { };
connectedClients[ clients[ client ] ].response.write( 'data: ' + JSON.stringify( { 'type': update, 'data': request.body.data } ) + '\n\n' );
if ( !clientReference[ String( request.query.room ) ] ) {
clientReference[ String( request.query.room ) ] = [];
}
if ( !clientReference[ String( request.query.room ) ]
.includes( sid ) ) {
clientReference[ String( request.query.room ) ].push( sid );
}
request.on( 'close', () => {
try {
importantClients[ sid ] = undefined;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch ( e ) { /* empty */ }
const cl = clientReference[ String( request.query.room ) ];
for ( const c in cl ) {
if ( cl[ c ] === sid ) {
cl.splice( parseInt( c ), 1 );
break;
} }
} }
response.send( 'ok' );
} else { connectedClients[ sid ] = undefined;
response.status( 404 ).send( 'ERR_CANNOT_SEND' ); } );
}
} else { } else {
response.status( 403 ).send( 'ERR_UNAUTHORIZED' ); response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
}
}
} else {
response.status( 400 ).send( 'ERR_WRONG_REQUEST' );
}
} );
app.post( '/socket/deleteRoom', bodyParser.json(), ( request: express.Request, response: express.Response ) => {
if ( request.body.roomName ) {
if ( socketData[ request.body.roomName ] ) {
if ( socketData[ request.body.roomName ].roomToken === request.body.roomToken ) {
socketData[ request.body.roomName ] = undefined;
const clients = clientReference[ request.body.roomName ];
for ( let client in clients ) {
if ( connectedClients[ clients[ client ] ] ) {
connectedClients[ clients[ client ] ].response.write( 'data: ' + JSON.stringify( { 'type': 'delete-share', 'data': true } ) + '\n\n' );
}
}
} else {
response.send( 403 ).send( 'ERR_UNAUTHORIZED' );
} }
} else { } else {
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' ); response.status( 404 ).send( 'ERR_NO_ROOM_SPECIFIED' );
} }
} else {
response.status( 400 ).send( 'ERR_NO_ROOM_NAME' );
} }
} ); );
app.get(
'/socket/getData',
( request: express.Request, response: express.Response ) => {
if ( request.query.room ) {
response.send( socketData[ String( request.query.room ) ] );
} else {
response.status( 400 ).send( 'ERR_NO_ROOM_SPECIFIED' );
}
}
);
app.get(
'/socket/joinRoom',
( request: express.Request, response: express.Response ) => {
if ( request.query.room ) {
if ( socketData[ String( request.query.room ) ] ) {
response.send( 'ok' );
} else {
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
}
} else {
response.status( 404 ).send( 'ERR_NO_ROOM_SPECIFIED' );
}
}
);
app.post(
'/socket/update',
bodyParser.json(),
( request: express.Request, response: express.Response ) => {
if ( socketData[ request.body.roomName ] ) {
if ( request.body.event === 'tampering' ) {
const clients = clientReference[ request.body.roomName ];
for ( const client in clients ) {
if ( importantClients[ clients[ client ] ] ) {
importantClients[ clients[ client ] ]
.response.write( 'data: ' + JSON.stringify( {
'type': 'tampering-msg',
'data': true
} ) + '\n\n' );
}
}
response.send( 'ok' );
} else {
if (
socketData[ request.body.roomName ].roomToken
=== request.body.roomToken
) {
let send = false;
let update = '';
if ( request.body.event === 'playback-start-update' ) {
send = true;
update = 'playback-start';
socketData[ request.body.roomName ]
.playbackStart = request.body.data;
} else if ( request.body.event === 'playback-update' ) {
send = true;
update = 'playback';
socketData[ request.body.roomName ]
.playbackStatus = request.body.data;
} else if ( request.body.event === 'playlist-update' ) {
send = true;
update = 'playlist';
socketData[ request.body.roomName ]
.playlist = request.body.data;
} else if ( request.body.event === 'playlist-index-update' ) {
send = true;
update = 'playlist-index';
socketData[ request.body.roomName ]
.playlistIndex = request.body.data;
}
if ( send ) {
const clients = clientReference[ request.body.roomName ];
for ( const client in clients ) {
if ( connectedClients[ clients[ client ] ] ) {
connectedClients[ clients[ client ] ]
.response.write( 'data: ' + JSON.stringify( {
'type': update,
'data': request.body.data
} ) + '\n\n' );
}
}
response.send( 'ok' );
} else {
response.status( 404 ).send( 'ERR_CANNOT_SEND' );
}
} else {
response.status( 403 ).send( 'ERR_UNAUTHORIZED' );
}
}
} else {
response.status( 400 ).send( 'ERR_WRONG_REQUEST' );
}
}
);
app.post(
'/socket/deleteRoom',
bodyParser.json(),
( request: express.Request, response: express.Response ) => {
if ( request.body.roomName ) {
if ( socketData[ request.body.roomName ] ) {
if (
socketData[ request.body.roomName ].roomToken
=== request.body.roomToken
) {
socketData[ request.body.roomName ] = undefined;
const clients = clientReference[ request.body.roomName ];
for ( const client in clients ) {
if ( connectedClients[ clients[ client ] ] ) {
connectedClients[ clients[ client ] ]
.response.write( 'data: ' + JSON.stringify( {
'type': 'delete-share',
'data': true
} ) + '\n\n' );
}
}
} else {
response.send( 403 ).send( 'ERR_UNAUTHORIZED' );
}
} else {
response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' );
}
} else {
response.status( 400 ).send( 'ERR_NO_ROOM_NAME' );
}
}
);
/* /*
GENERAL ROUTES GENERAL ROUTES
*/ */
app.get( '/', ( request: express.Request, response: express.Response ) => { app.get( '/', ( _request: express.Request, response: express.Response ) => {
response.send( 'Please visit <a href="https://music.janishutz.com">https://music.janishutz.com</a> to use this service' ); response.send( 'Please visit <a href="https://music.janishutz.com">https://music.janishutz.com</a> to use this service' );
} ); } );
app.get( '/createRoomToken', ( request: express.Request, response: express.Response ) => { app.get(
if ( sdk.checkAuth( request ) ) { '/createRoomToken',
const roomName = String( request.query.roomName ) ?? ''; ( request: express.Request, response: express.Response ) => {
if ( !socketData[ roomName ] ) { if ( sdk.checkAuth( request ) ) {
const roomToken = crypto.randomUUID(); // eslint-disable-next-line no-constant-binary-expression
socketData[ roomName ] = { const roomName = String( request.query.roomName ) ?? '';
playbackStart: 0,
playbackStatus: false, if ( !socketData[ roomName ] ) {
playlist: [], const roomToken = crypto.randomUUID();
playlistIndex: 0,
roomName: roomName, socketData[ roomName ] = {
roomToken: roomToken, 'playbackStart': 0,
ownerUID: sdk.getUserData( request ).uid, 'playbackStatus': false,
useAntiTamper: request.query.useAntiTamper === 'true' ? true : false, 'playlist': [],
}; 'playlistIndex': 0,
response.send( roomToken ); 'roomName': roomName,
} else { 'roomToken': roomToken,
if ( socketData[ roomName ].ownerUID === sdk.getUserData( request ).uid ) { 'ownerUID': sdk.getUserData( request ).uid,
response.send( socketData[ roomName ].roomToken ); 'useAntiTamper': request.query.useAntiTamper === 'true'
? true : false,
};
response.send( roomToken );
} else { } else {
response.status( 409 ).send( 'ERR_CONFLICT' ); if (
socketData[ roomName ].ownerUID
=== sdk.getUserData( request ).uid
) {
response.send( socketData[ roomName ].roomToken );
} else {
response.status( 409 ).send( 'ERR_CONFLICT' );
}
} }
} else {
response.status( 403 ).send( 'ERR_FORBIDDEN' );
} }
} else {
response.status( 403 ).send( 'ERR_FORBIDDEN' );
} }
} ); );
app.get( '/getAppleMusicDevToken', ( req, res ) => { app.get( '/getAppleMusicDevToken', ( req, res ) => {
// sign dev token checkIfOwned( req ).then( owned => {
const privateKey = fs.readFileSync( path.join( __dirname + '/config/apple_private_key.p8' ) ).toString(); if ( owned ) {
// TODO: Remove secret // sign dev token
const config = JSON.parse( '' + fs.readFileSync( path.join( __dirname + '/config/apple-music-api.config.secret.json' ) ) ); const privateKey = fs.readFileSync( path.join(
const now = new Date().getTime(); __dirname,
const tomorrow = now + 24 * 3600 * 1000; '/config/apple_private_key.p8'
const jwtToken = jwt.sign( { ) ).toString();
'iss': config.teamID, const config = JSON.parse( fs.readFileSync( path.join(
'iat': Math.floor( now / 1000 ), __dirname,
'exp': Math.floor( tomorrow / 1000 ), '/config/apple-music-api.config.secret.json'
}, privateKey, { ) ).toString() );
algorithm: "ES256",
keyid: config.keyID
} );
res.send( jwtToken );
} );
// TODO: Get user's subscriptions using store sdk
app.get( '/checkUserStatus', ( request: express.Request, response: express.Response ) => {
if ( sdk.checkAuth( request ) ) {
storeSDK.getSubscriptions( sdk.getUserData( request ).uid ).then( stat => {
let owned = false;
const now = new Date().getTime(); const now = new Date().getTime();
for ( let sub in stat ) { const tomorrow = now + ( 24 * 3600 * 1000 );
if ( stat[ sub ].expires - now > 0 const jwtToken = jwt.sign( {
&& ( stat[ sub ].id === 'com.janishutz.MusicPlayer.subscription' || stat[ sub ].id === 'com.janishutz.MusicPlayer.subscription-month' ) ) { 'iss': config.teamID,
owned = true; 'iat': Math.floor( now / 1000 ),
} 'exp': Math.floor( tomorrow / 1000 ),
} }, privateKey, {
if ( owned ) { 'algorithm': 'ES256',
response.send( 'ok' ); 'keyid': config.keyID
} );
res.send( jwtToken );
} else {
res.status( 402 ).send( 'ERR_NOT_OWNED' );
}
} )
.catch( e => {
if ( e === 'ERR_NOT_OWNED' ) {
res.status( 402 ).send( e );
} else if ( e === 'ERR_AUTH_REQUIRED' ) {
res.status( 401 ).send( e );
} else { } else {
response.send( 'ERR_NOT_OWNED' ); res.send( 500 ).send( e );
} }
} ).catch( e => {
console.error( e );
response.status( 404 ).send( 'ERR_NOT_OWNED' );
} ); } );
} else {
response.status( 401 ).send( 'ERR_AUTH_REQUIRED' );
}
} ); } );
app.use( ( request: express.Request, response: express.Response, next: express.NextFunction ) => {
response.status( 404 ).send( 'ERR_NOT_FOUND' ); const ownedCache = {};
// response.sendFile( path.join( __dirname + '' ) )
const checkIfOwned = ( request: express.Request ): Promise<boolean> => {
return new Promise( ( resolve, reject ) => {
if ( sdk.checkAuth( request ) ) {
const userData = sdk.getUserData( request );
if ( ownedCache[ userData.uid ] ) {
resolve( ownedCache[ userData.uid ] );
} else {
storeSDK.getSubscriptions( userData.uid )
.then( stat => {
const now = new Date().getTime();
for ( const sub in stat ) {
if ( stat[ sub ].expires - now > 0
&& (
stat[ sub ].id
=== 'com.janishutz.MusicPlayer.subscription'
|| stat[ sub ].id
=== 'com.janishutz.MusicPlayer.subscription-month'
)
) {
ownedCache[ userData.uid ] = true;
resolve( true );
}
}
ownedCache[ userData.uid ] = false;
resolve( false );
} )
.catch( e => {
console.error( e );
reject( 'ERR_NOT_OWNED' );
} );
}
} else {
reject( 'ERR_AUTH_REQUIRED' );
}
} );
};
app.get(
'/checkUserStatus',
( request: express.Request, response: express.Response ) => {
checkIfOwned( request )
.then( owned => {
if ( owned ) {
response.send( 'ok' );
} else {
response.status( 402 ).send( 'ERR_NOT_OWNED' );
}
} )
.catch( e => {
if ( e === 'ERR_NOT_OWNED' ) {
response.status( 402 ).send( e );
} else if ( e === 'ERR_AUTH_REQUIRED' ) {
response.status( 401 ).send( e );
} else {
response.send( 500 ).send( e );
}
} );
}
);
app.use( ( request: express.Request, response: express.Response ) => {
response.status( 404 ).send( 'ERR_NOT_FOUND: ' + request.path );
} ); } );
const PORT = process.env.PORT || 8082; const PORT = process.env.PORT || 8082;
httpServer.listen( PORT ); httpServer.listen( PORT );
} };
export default { export default {
run run
} };