diff --git a/package-lock.json b/package-lock.json index c8e23879..9e703ed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@dead50f7/adbkit": "^2.11.4", "express": "^4.21.2", + "express-basic-auth": "^1.2.1", "ios-device-lib": "^0.9.2", "node-mjpeg-proxy": "^0.3.2", "node-pty": "^0.10.1", @@ -2519,7 +2520,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "optional": true, "dependencies": { "safe-buffer": "5.1.2" }, @@ -2530,8 +2530,7 @@ "node_modules/basic-auth/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/basic-ftp": { "version": "5.0.5", @@ -4116,6 +4115,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-basic-auth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz", + "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==", + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", diff --git a/package.json b/package.json index cc92a219..dd4339ea 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dist:prod": "webpack --config webpack/ws-scrcpy.prod.ts --stats-error-details", "dist": "npm run dist:prod", "start": "npm run dist && cd dist && npm start", + "dev": "npm run dist:dev && cd dist && npm start", "script:dist:start": "node ./index.js", "lint": "eslint src/ --ext .ts", "format": "eslint src/ --fix --ext .ts", @@ -18,6 +19,7 @@ "dependencies": { "@dead50f7/adbkit": "^2.11.4", "express": "^4.21.2", + "express-basic-auth": "^1.2.1", "ios-device-lib": "^0.9.2", "node-mjpeg-proxy": "^0.3.2", "node-pty": "^0.10.1", diff --git a/src/app/DisplayInfo.ts b/src/app/DisplayInfo.ts index ea3c0114..3d16ae5e 100644 --- a/src/app/DisplayInfo.ts +++ b/src/app/DisplayInfo.ts @@ -16,7 +16,15 @@ export class DisplayInfo { public readonly rotation: number, public readonly layerStack: number, public readonly flags: number, - ) {} + ) { + const deviceViewElem = document.getElementsByClassName('device-view')[0] as HTMLElement; + if( rotation && deviceViewElem ){ + deviceViewElem.style.maxWidth = "none"; + } + else if( deviceViewElem ){ + deviceViewElem.style.maxWidth = "460px"; + } + } public toBuffer(): Buffer { const temp = Buffer.alloc(DisplayInfo.BUFFER_LENGTH); diff --git a/src/app/client/BaseDeviceTracker.ts b/src/app/client/BaseDeviceTracker.ts index 1e045ae4..c97f65a1 100644 --- a/src/app/client/BaseDeviceTracker.ts +++ b/src/app/client/BaseDeviceTracker.ts @@ -105,13 +105,19 @@ export abstract class BaseDeviceTracker
{ + // console.log(TAG, 'Building row (after initiating refresh)', item); this.buildDeviceRow(block, item); }); + } private setNameValue(parent: Element | null, name: string): void { diff --git a/src/app/client/StreamReceiver.ts b/src/app/client/StreamReceiver.ts index 0705543a..bf99df19 100644 --- a/src/app/client/StreamReceiver.ts +++ b/src/app/client/StreamReceiver.ts @@ -25,6 +25,7 @@ export type DisplayCombinedInfo = { interface StreamReceiverEvents { video: ArrayBuffer; deviceMessage: DeviceMessage; + rotated: any; displayInfo: DisplayCombinedInfo[]; clientsStats: ClientsStats; encoders: string[]; @@ -67,6 +68,7 @@ export class StreamReceiver

extends ManagerClient diff --git a/src/app/googDevice/client/StreamClientScrcpy.ts b/src/app/googDevice/client/StreamClientScrcpy.ts index 29e8f9ca..110d656b 100644 --- a/src/app/googDevice/client/StreamClientScrcpy.ts +++ b/src/app/googDevice/client/StreamClientScrcpy.ts @@ -30,6 +30,7 @@ import { ACTION } from '../../../common/Action'; import { StreamReceiverScrcpy } from './StreamReceiverScrcpy'; import { ParamsDeviceTracker } from '../../../types/ParamsDeviceTracker'; import { ScrcpyFilePushStream } from '../filePush/ScrcpyFilePushStream'; +import genericAndroid from "../../../common/generic_android.png" type StartParams = { udid: string; @@ -320,6 +321,11 @@ export class StreamClientScrcpy deviceView.appendChild(this.controlButtons); const video = document.createElement('div'); video.className = 'video'; + const genericAndroidMockup = document.createElement("img"); + genericAndroidMockup.src = genericAndroid; + genericAndroidMockup.id = "generic-android-mockup"; + genericAndroidMockup.style.height = ""; + video.appendChild(genericAndroidMockup); deviceView.appendChild(video); deviceView.appendChild(moreBox); player.setParent(video); @@ -340,6 +346,7 @@ export class StreamClientScrcpy const streamReceiver = this.streamReceiver; streamReceiver.on('deviceMessage', this.OnDeviceMessage); + streamReceiver.on('rotated', ()=>{ this.player?.reOrientScreen(true, this.player) }); streamReceiver.on('video', this.onVideo); streamReceiver.on('clientsStats', this.onClientsStats); streamReceiver.on('displayInfo', this.onDisplayInfo); diff --git a/src/app/googDevice/toolbox/GoogToolBox.ts b/src/app/googDevice/toolbox/GoogToolBox.ts index 83aeb8dd..78be73dc 100644 --- a/src/app/googDevice/toolbox/GoogToolBox.ts +++ b/src/app/googDevice/toolbox/GoogToolBox.ts @@ -101,7 +101,7 @@ export class GoogToolBox extends ToolBox { const element = el.getElement(); moreBox.style.display = element.checked ? 'block' : 'none'; }); - elements.unshift(more); + // elements.unshift(more); } return new GoogToolBox(elements); } diff --git a/src/app/index.ts b/src/app/index.ts index cbc7a281..32d04fd8 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -3,9 +3,24 @@ import { StreamClientScrcpy } from './googDevice/client/StreamClientScrcpy'; import { HostTracker } from './client/HostTracker'; import { Tool } from './client/Tool'; + window.onload = async function (): Promise { + // Redirect if the URL contains a hash fragment const hash = location.hash.replace(/^#!/, ''); - const parsedQuery = new URLSearchParams(hash); + if (hash) { + + const parsedQuery = new URLSearchParams(hash); + + // Add `hasHash=true` to the query parameters + parsedQuery.set('hasHash', 'true'); + + // Redirect to a query-based URL + window.location.href = `/?${parsedQuery.toString()}`; + return; // Stop further execution + } + + // If no hash fragment, proceed with the rest of the logic + const parsedQuery = new URLSearchParams(location.search); // Use query parameters instead of hash const action = parsedQuery.get('action'); /// #if USE_BROADWAY diff --git a/src/app/player/BasePlayer.ts b/src/app/player/BasePlayer.ts index ab881ce5..5f1f3ca8 100644 --- a/src/app/player/BasePlayer.ts +++ b/src/app/player/BasePlayer.ts @@ -46,7 +46,7 @@ export interface PlayerClass { fitToScreen: boolean, displayInfo?: DisplayInfo, ): void; - new (udid: string, displayInfo?: DisplayInfo): BasePlayer; + new(udid: string, displayInfo?: DisplayInfo): BasePlayer; } export abstract class BasePlayer extends TypedEmitter { @@ -113,6 +113,39 @@ export abstract class BasePlayer extends TypedEmitter { super(); this.touchableCanvas = document.createElement('canvas'); this.touchableCanvas.className = 'touch-layer'; + this.touchableCanvas.style.width = "calc(100vw - 3rem)"; + if (window.innerWidth > 380) + this.touchableCanvas.style.maxWidth = "315px"; + else + this.touchableCanvas.style.maxWidth = "80vw"; + + + const myInterval = setInterval(() => { + if (tag.clientHeight || tag.clientWidth) { + this.reOrientScreen(); + + window.addEventListener('resize', () => { + this.reOrientScreen(); + }); + + window.addEventListener('message', (e) => { + const allowedOrigins = [ + "https://trust-me-bro.nativebridge.io", + "http://localhost:5173", + ]; + + if (!allowedOrigins.includes(e.origin)) { + console.warn("Blocked message from untrusted origin:", e.origin); + return; // Reject messages from untrusted origins + } + if(e.data.event === "screenshot"){ + window.top?.postMessage({ event: "screenshot", imageUrl: this.getImageDataURL() }, "*"); // Replace '*' with the specific origin for security + } + }); + + clearInterval(myInterval); + } + }, 500); this.touchableCanvas.oncontextmenu = function (event: MouseEvent): void { event.preventDefault(); }; @@ -120,6 +153,211 @@ export abstract class BasePlayer extends TypedEmitter { this.videoSettings = BasePlayer.getVideoSettingFromStorage(preferred, this.storageKeyPrefix, udid, displayInfo); } + protected sendDataToParent(rotation: number | undefined): void { + // Send data to the parent window + + const videoElemParent = document.getElementsByClassName("video")[0] as HTMLElement; + const videoElem = document.getElementsByClassName("video-layer")[0] as HTMLElement; + const touchElem = document.getElementsByClassName("touch-layer")[0] as HTMLElement; + + const remToPx = (rem: number) => rem * parseFloat(getComputedStyle(document.documentElement).fontSize); + + const androidFrame = document.getElementById("generic-android-mockup"); + + if (androidFrame) { + // androidFrame.style.width = "calc(100vw - 3rem)"; + + + const offset = remToPx(3); // Convert 3rem to pixels + let initialWidth; + + if( rotation ){ + if( window.innerWidth <= 380 ) + initialWidth = window.innerWidth - offset; + else + initialWidth = window.innerWidth - offset > 940 ? 940 : window.innerWidth - offset + } + else { + + if( window.innerWidth <= 380 ) + initialWidth = window.innerWidth - offset; + else + initialWidth = window.innerWidth - offset > 420 ? 420 : window.innerWidth - offset + } + // const initialWidth = window.innerWidth <= 380 ? window.innerWidth - offset : ; + // androidFrame.style.transformOrigin = Math.abs((initialWidth) / 2) + "px " + Math.abs((initialWidth) / 2) + "px"; + + if (window.innerWidth > 380){ + + if( rotation ){ + + androidFrame.style.transform = "rotateZ(-90deg)"; + androidFrame.style.transformOrigin = Math.abs((initialWidth * ( 191/320 )) / 2) + "px " + Math.abs((initialWidth * ( 191/320 )) / 2) + "px"; + androidFrame.style.width = "auto"; + videoElemParent.style.width = initialWidth + "px"; + androidFrame.style.height = initialWidth + "px"; + androidFrame.style.aspectRatio = "191/320"; + // androidFrame.style.scale = "1.07"; + androidFrame.style.marginTop = "0px"; + androidFrame.style.marginLeft = "0px"; + } + else { + + androidFrame.style.transform = ""; + androidFrame.style.transformOrigin = Math.abs((initialWidth) / 2) + "px " + Math.abs((initialWidth) / 2) + "px"; + androidFrame.style.width = initialWidth + "px"; + androidFrame.style.height = "auto"; + androidFrame.style.aspectRatio = "47/80"; + // androidFrame.style.scale = "1.07"; + androidFrame.style.marginTop = "0px"; + androidFrame.style.marginLeft = "0px"; + } + + } + else{ + + if (rotation) { + androidFrame.style.maxWidth = "90vw"; + + console.log("initialWidth ", initialWidth); + androidFrame.style.transform = "rotateZ(-90deg)"; + androidFrame.style.transformOrigin = Math.abs(((initialWidth)*( 37 / 64 )) / 2) + "px " + Math.abs(((initialWidth)*( 37 / 64 )) / 2) + "px"; + androidFrame.style.width = "auto"; + androidFrame.style.height = "calc(100vw - 3rem)"; + androidFrame.style.aspectRatio = "38/64"; + androidFrame.style.scale = "1"; + androidFrame.style.marginTop = "5px"; + androidFrame.style.marginLeft = "0px"; + + } + else { + androidFrame.style.transform = ""; + androidFrame.style.transformOrigin = Math.abs((initialWidth) / 2) + "px " + Math.abs((initialWidth) / 2) + "px"; + androidFrame.style.width = initialWidth + "px"; + androidFrame.style.aspectRatio = "95/161" //"151/256"; + // androidFrame.style.scale = "1.01"; + androidFrame.style.marginTop = "3px"; + androidFrame.style.marginLeft = "0px"; + } + } + } + + if (videoElem) { + if (rotation) { + + if (window.innerWidth > 380){ + videoElem.style.width = "calc(100vw - 4.5rem)"; + videoElem.style.maxWidth = "910px"; + videoElem.style.borderRadius = "1.5rem"; + videoElem.style.marginTop = "3.4%"; + videoElem.style.marginLeft = "11px"; + } + else{ + + videoElem.style.width = "calc(100vw - 4rem)"; + videoElem.style.maxWidth = "84vw"; + videoElem.style.borderRadius = "1rem"; + videoElem.style.marginTop = "14px"; + videoElem.style.marginLeft = "15px"; + } + } + else { + + videoElem.style.width = "calc(100vw - 3rem)"; + + if (window.innerWidth > 380){ + + videoElem.style.width = "calc(100vw - 5.5rem)"; + videoElem.style.maxWidth = "380px"; + videoElem.style.marginTop = "4%"; + videoElem.style.marginLeft = "4%"; + } + else{ + + videoElem.style.width = "calc(100vw - 4rem)"; + videoElem.style.maxWidth = "80vw"; + videoElem.style.marginTop = "4%"; + videoElem.style.marginLeft = "4.5%"; + } + } + } + if (touchElem) { + if (rotation) { + + if (window.innerWidth > 380){ + touchElem.style.width = "calc(100vw - 4.5rem)"; + touchElem.style.maxWidth = "910px"; + touchElem.style.borderRadius = "1.5rem"; + touchElem.style.marginTop = "3.4%"; + touchElem.style.marginLeft = "11px"; + } + else{ + + touchElem.style.width = "calc(100vw - 4rem)"; + touchElem.style.maxWidth = "84vw"; + touchElem.style.borderRadius = "1rem"; + touchElem.style.marginTop = "11px"; + touchElem.style.marginLeft = "7px"; + } + } + else { + + if (window.innerWidth > 380){ + touchElem.style.width = "calc(100vw - 5.5rem)"; + touchElem.style.maxWidth = "380px"; + touchElem.style.marginTop = "4%"; + touchElem.style.marginLeft = "4%"; + } + else{ + + touchElem.style.width = "calc(100vw - 4rem)"; + touchElem.style.maxWidth = "80vw"; + touchElem.style.marginTop = "4%"; + touchElem.style.marginLeft = "4.5%"; + } + } + } + + window.top?.postMessage({ event: "device-rotation", rotation: rotation }, "*"); // Replace '*' with the specific origin for security + } + + public reOrientScreen(invert: boolean = false, player: BasePlayer = this): void { + + // if( !player ){ + // console.log("player not found"); + // player = this; + // } + + // else { + // console.log("player found"); + // } + + // const deviceMockup = document.getElementsByClassName("generic-android-mockup")[0] as HTMLElement; + // console.log("invert ", invert, player.displayInfo?.rotation, player.videoWidth, player.videoHeight); + let rotation = invert ? !player?.displayInfo?.rotation : player?.displayInfo?.rotation as number | boolean; + + this.sendDataToParent(player?.displayInfo?.rotation); + + if (rotation) { + + // deviceMockup.style.height = player.videoWidth + "px"; + // deviceMockup.style.width = (player.videoHeight + 23) + "px"; + // player.touchableCanvas.style.width = player.videoWidth + "px"; + // player.touchableCanvas.style.height = (player.videoHeight) + "px"; + // deviceMockup.style.transform = "translate(54%, -26.5%) rotateZ(90deg)"; + } + else { + // deviceMockup.style.width = player.videoWidth + "px"; + // deviceMockup.style.height = (player.videoHeight + 23) + "px"; + // player.touchableCanvas.style.width = player.videoWidth + "px"; + // player.touchableCanvas.style.height = (player.videoHeight) + "px"; + // deviceMockup.style.transform = "none"; + } + + player.touchableCanvas.style.zIndex = "20"; + } + + protected calculateScreenInfoForBounds(videoWidth: number, videoHeight: number): void { this.videoWidth = videoWidth; this.videoHeight = videoHeight; diff --git a/src/app/player/MsePlayer.ts b/src/app/player/MsePlayer.ts index 3f645ed5..ffcd3e07 100644 --- a/src/app/player/MsePlayer.ts +++ b/src/app/player/MsePlayer.ts @@ -40,6 +40,8 @@ export class MsePlayer extends BasePlayer { tag.id = id; } tag.className = 'video-layer'; + tag.style.width = "calc(100vw - 3rem)"; + tag.style.maxWidth = "315px"; return tag; } diff --git a/src/common/generic_android.png b/src/common/generic_android.png new file mode 100644 index 00000000..c8789769 Binary files /dev/null and b/src/common/generic_android.png differ diff --git a/src/public/index.html b/src/public/index.html index 394398c4..74ac2d38 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -4,7 +4,56 @@ WS scrcpy + + + - + \ No newline at end of file diff --git a/src/server/goog-device/mw/DeviceTracker.ts b/src/server/goog-device/mw/DeviceTracker.ts index 18360918..70c597d4 100644 --- a/src/server/goog-device/mw/DeviceTracker.ts +++ b/src/server/goog-device/mw/DeviceTracker.ts @@ -37,7 +37,11 @@ export class DeviceTracker extends Mw { .init() .then(() => { this.adt.on('device', this.sendDeviceMessage); - this.buildAndSendMessage(this.adt.getDevices()); + let deviceList = this.adt.getDevices(); + // console.log(`[${DeviceTracker.TAG}] deviceList: ${JSON.stringify(deviceList)}\n`); + // Filter out devices with state 'offline' or 'disconnected' + deviceList = deviceList.filter((device: any) => device.state !== 'offline' && device.state !== 'disconnected'); + this.buildAndSendMessage(deviceList); }) .catch((error: Error) => { console.error(`[${DeviceTracker.TAG}] Error: ${error.message}`); diff --git a/src/server/goog-device/services/ControlCenter.ts b/src/server/goog-device/services/ControlCenter.ts index 168852ff..1ce14777 100644 --- a/src/server/goog-device/services/ControlCenter.ts +++ b/src/server/goog-device/services/ControlCenter.ts @@ -57,32 +57,43 @@ export class ControlCenter extends BaseControlCenter imple private onChangeSet = (changes: TrackerChangeSet): void => { this.waitAfterError = ControlCenter.defaultWaitAfterError; if (changes.added.length) { + // console.log(`Changes added: ${JSON.stringify(changes.added)}`); for (const item of changes.added) { const { id, type } = item; + // console.log(`(Added on change list) Handling device connection for id : ${id} type: ${type}\n`); this.handleConnected(id, type); + + } } if (changes.removed.length) { for (const item of changes.removed) { const { id } = item; + // console.log(`(Removed on change list) Handling device connection for id : ${id}\n`); this.handleConnected(id, DeviceState.DISCONNECTED); } } + if (changes.changed.length) { for (const item of changes.changed) { const { id, type } = item; + // console.log(`(Changed on change list) Handling device connection for id : ${id} type: ${type}\n`); this.handleConnected(id, type); } } }; private onDeviceUpdate = (device: Device): void => { - const { udid, descriptor } = device; - this.descriptors.set(udid, descriptor); + // console.log(`(On device update) Handling device update for udid: ${device.udid} and state: ${device.descriptor.state}\n`); + const { udid, descriptor } = device; + if (descriptor.state === 'device' || descriptor.state === 'emulator') { + this.descriptors.set(udid, descriptor); + } this.emit('device', descriptor); }; private handleConnected(udid: string, state: string): void { + // console.log(`Device connected: ${udid} state: ${state}\n`); let device = this.deviceMap.get(udid); if (device) { device.setState(state); @@ -93,7 +104,9 @@ export class ControlCenter extends BaseControlCenter imple } } + public async init(): Promise { + // console.log(`Initializing "${this.getName()}"`); if (this.initialized) { return; } @@ -101,12 +114,14 @@ export class ControlCenter extends BaseControlCenter imple const list = await this.client.listDevices(); list.forEach((device) => { const { id, type } = device; + // console.log(`(Initialization) handling device connection for id: ${id} type: ${type}\n`); this.handleConnected(id, type); }); this.initialized = true; } private async startTracker(): Promise { + // console.log(`Starting tracker`); if (this.tracker) { return this.tracker; } @@ -118,6 +133,7 @@ export class ControlCenter extends BaseControlCenter imple } private stopTracker(): void { + // console.log(`Stopping tracker`); if (this.tracker) { this.tracker.off('changeSet', this.onChangeSet); this.tracker.off('end', this.restartTracker); diff --git a/src/server/services/HttpServer.ts b/src/server/services/HttpServer.ts index f941dfcf..2699bbd9 100644 --- a/src/server/services/HttpServer.ts +++ b/src/server/services/HttpServer.ts @@ -3,11 +3,14 @@ import * as https from 'https'; import path from 'path'; import { Service } from './Service'; import { Utils } from '../Utils'; -import express, { Express } from 'express'; +import express, { Express, Request, Response, NextFunction } from 'express'; import { Config } from '../Config'; import { TypedEmitter } from '../../common/TypedEmitter'; import * as process from 'process'; import { EnvName } from '../EnvName'; +import basicAuth from 'express-basic-auth'; // Add this for basic authentication +import * as crypto from 'crypto'; + const DEFAULT_STATIC_DIR = path.join(__dirname, './public'); @@ -74,8 +77,99 @@ export class HttpServer extends TypedEmitter implements Servic return `HTTP(s) Server Service`; } + public verifySignature(req: Request): boolean { + const SECRET_KEY = process.env.SIGNATURE_SECRET_KEY as string; + console.log('SECRET_KEY:', SECRET_KEY); + if (!SECRET_KEY) { + throw new Error('Environment variables SECRET_KEY must be set'); + } + + // Construct the full URL including protocol, host, and path + const host = req.get('host'); // Get the host from the request headers + let protocol: string; + if (host?.startsWith('localhost')) { + protocol = 'http'; + } + else{ + protocol = 'https'; + } + const path = req.originalUrl.split('&signature=')[0]; // Get the path and query without the signature + + const fullUrl = `${protocol}://${host}${path}`; + + // Extract the received signature + const receivedSignature = req.query.signature as string; + + + // Create a HMAC-SHA256 hash of the URL using the secret key + const hmac = crypto.createHmac('sha256', Buffer.from(SECRET_KEY, 'utf8')); // Use 'utf8' if SECRET_KEY is plain text + hmac.update(fullUrl); + const calculatedSignature = hmac.digest('base64'); + + + // Convert base64 to base64url + const base64urlSignature = calculatedSignature + .replace(/\+/g, '-') // Replace '+' with '-' + .replace(/\//g, '_') // Replace '/' with '_' + .replace(/=+$/, ''); // Remove padding '=' characters + + + // Compare the calculated signature with the received signature + return receivedSignature === base64urlSignature; + } + + + public async start(): Promise { this.mainApp = express(); + + // Ensure environment variables are defined + const adminUser = process.env.ADMIN_USER; + const adminPassword = process.env.ADMIN_PASSWORD; + + if (!adminUser || !adminPassword) { + throw new Error('Environment variables ADMIN_USER and ADMIN_PASSWORD must be set'); + } + + // Add basic authentication middleware for the base path + const authMiddleware = basicAuth({ + users: { + [adminUser]: adminPassword + }, + challenge: true, + realm: 'Restricted Access', + }); + + // Protect the base path + this.mainApp.use((req: Request, res: Response, next: NextFunction) => { + if (req.path === '/' && req.query.hasHash==='true' && req.query.signature) { + // Verify the signature for the base URL + if (this.verifySignature(req)) { + next(); + } else { + res.status(403).send('Invalid signature'); + } + } else if (req.path === '/') { + // Apply authentication for all other cases (including the base URL) + authMiddleware(req, res, next); + } else { + // Allow access to other paths + next(); + } + }); + + + // this.mainApp.use((req: Request, _res: Response, next: NextFunction) => { + // console.log('Protocol:', req.protocol); // Log the protocol (http or https) + // next(); + // }); + + this.mainApp.post('/logout', (_req: Request, res: Response) => { + console.log('Logging out'); + res.setHeader('WWW-Authenticate', 'Basic realm="Restricted Access"'); + res.status(401).send('Logged out successfully'); + }); + if (HttpServer.SERVE_STATIC && HttpServer.PUBLIC_DIR) { this.mainApp.use(PATHNAME, express.static(HttpServer.PUBLIC_DIR)); @@ -85,6 +179,7 @@ export class HttpServer extends TypedEmitter implements Servic this.mainApp.get('/mjpeg/:udid', new MjpegProxyFactory().proxyRequest); /// #endif } + const config = Config.getInstance(); config.servers.forEach((serverItem) => { const { secure, port, redirectToSecure } = serverItem; @@ -140,4 +235,4 @@ export class HttpServer extends TypedEmitter implements Servic item.server.close(); }); } -} +} \ No newline at end of file diff --git a/src/style/app.css b/src/style/app.css index e13218c2..5a939574 100644 --- a/src/style/app.css +++ b/src/style/app.css @@ -1,5 +1,5 @@ :root { - --main-bg-color: hsl(0, 0%, 100%); + --main-bg-color: white; --stream-bg-color: hsl(0, 0%, 85%); --shell-bg-color: hsl(0, 0%, 0%); --text-shadow-color: hsl(218, 67%, 95%); @@ -79,7 +79,7 @@ body.shell { } body.stream { - background-color: var(--stream-bg-color); + background-color: var(--main-bg-color); } .terminal-container { @@ -104,38 +104,60 @@ body.stream { .device-view { z-index: 1; float: right; - display: inline-block; + display: flex; + flex-direction: row; + width: 100%; + max-width: 460px; + justify-content: start; } .video-layer { position: absolute; z-index: 0; + border-radius: 1.5rem; } .touch-layer { position: absolute; + border-radius: 1.5rem; z-index: 1; } .video { - float: right; + /* float: right; */ max-height: 100%; max-width: 100%; - background-color: #000000; + background-color: transparent; + position: relative; +} + +.mockup-container { + position: relative; + width: 100%; + height: 100%; } +#generic-android-mockup { + position: absolute; + top: 0; + left: 0; + z-index: 10; +} .control-buttons-list { - float: right; - width: 3.715rem; + /* float: right; */ + width: 1.6rem; background-color: var(--control-buttons-bg-color); + padding-left: 8px; + padding-right: 8px; + /* margin-left: auto; */ + padding-top: 1rem; } .control-button { - margin: .357rem .786rem; padding: 0; - width: 2.143rem; - height: 2.143rem; + width: 1.5rem; + height: 1.5rem; border: none; opacity: 0.75; background-color: var(--control-buttons-bg-color);