diff --git a/.prettierignore b/.prettierignore index 15e3fb5..75752bc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,5 +5,4 @@ coverage *.tsbuildinfo bun.lock apps/tauri/src-tauri -apps/electron/release licenses diff --git a/apps/electron/package.json b/apps/electron/package.json index aa4d5a0..accc2d8 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -60,11 +60,7 @@ } ], "linux": { - "target": [ - "AppImage", - "deb", - "rpm" - ], + "target": ["AppImage", "deb", "rpm"], "icon": "build/icons", "executableName": "tensamin", "category": "Network", @@ -77,16 +73,11 @@ } }, "win": { - "target": [ - "nsis", - "portable" - ], + "target": ["nsis", "portable"], "icon": "build/icons/icon.ico" }, "mac": { - "target": [ - "dmg" - ], + "target": ["dmg"], "icon": "build/icons/icon.icns" }, "publish": null diff --git a/apps/electron/scripts/generate-release-metadata.ts b/apps/electron/scripts/generate-release-metadata.ts index 7e36096..36a6281 100644 --- a/apps/electron/scripts/generate-release-metadata.ts +++ b/apps/electron/scripts/generate-release-metadata.ts @@ -1,11 +1,5 @@ import { createHash } from "node:crypto"; -import { - createReadStream, - existsSync, - readdirSync, - statSync, - writeFileSync, -} from "node:fs"; +import { createReadStream, existsSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { basename, join } from "node:path"; import rootPackage from "../../../package.json" with { type: "json" }; @@ -61,10 +55,7 @@ const metadata = { artifacts, }; -writeFileSync( - join(outDir, "electron-release-metadata.json"), - `${JSON.stringify(metadata, null, 2)}\n`, -); +writeFileSync(join(outDir, "electron-release-metadata.json"), `${JSON.stringify(metadata, null, 2)}\n`); writeFileSync( join(outDir, "SHA256SUMS"), `${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`, diff --git a/apps/electron/src/main/main.ts b/apps/electron/src/main/main.ts index 05f88fc..c4ee840 100644 --- a/apps/electron/src/main/main.ts +++ b/apps/electron/src/main/main.ts @@ -1,19 +1,9 @@ import { execFile } from "node:child_process"; import { fileURLToPath } from "node:url"; import { dirname, join, resolve } from "node:path"; -import { - app, - BrowserWindow, - desktopCapturer, - ipcMain, - session, - shell, -} from "electron"; +import { app, BrowserWindow, desktopCapturer, ipcMain, session, shell } from "electron"; import { checkForUpdates } from "./updates.js"; -import { - ipcChannels, - type DesktopScreenShareCapabilities, -} from "../shared/ipc.js"; +import { ipcChannels, type DesktopScreenShareCapabilities } from "../shared/ipc.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const verbose = process.argv.includes("--verbose"); @@ -26,11 +16,7 @@ if (verbose) { app.commandLine.appendSwitch("log-level", "0"); } -if ( - process.platform === "linux" && - process.env.XDG_SESSION_TYPE === "wayland" && - !process.env.TENSAMIN_ENABLE_VULKAN -) { +if (process.platform === "linux" && process.env.XDG_SESSION_TYPE === "wayland" && !process.env.TENSAMIN_ENABLE_VULKAN) { app.commandLine.appendSwitch("disable-features", "Vulkan"); } @@ -101,8 +87,7 @@ async function listAudioOutputs() { if (!sink || typeof sink !== "object") return null; const record = sink as Record; const id = record.index == null ? undefined : String(record.index); - const name = - typeof record.description === "string" ? record.description : id; + const name = typeof record.description === "string" ? record.description : id; if (!id || !name) return null; return { id, name, isDefault: false }; }) @@ -128,34 +113,30 @@ async function listScreenShareSources() { } function registerDisplayMediaHandler() { - session.defaultSession.setDisplayMediaRequestHandler( - async (_request, callback) => { - verboseLog("display media request", { selectedScreenShareSourceId }); + session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => { + verboseLog("display media request", { selectedScreenShareSourceId }); - const sources = await desktopCapturer.getSources({ - types: ["screen", "window"], - thumbnailSize: { width: 0, height: 0 }, - }); + const sources = await desktopCapturer.getSources({ + types: ["screen", "window"], + thumbnailSize: { width: 0, height: 0 }, + }); - const selected = sources.find( - (source) => source.id === selectedScreenShareSourceId, - ); - selectedScreenShareSourceId = null; - const video = selected ?? sources[0]; + const selected = sources.find((source) => source.id === selectedScreenShareSourceId); + selectedScreenShareSourceId = null; + const video = selected ?? sources[0]; - if (!video) { - callback({}); - return; - } + if (!video) { + callback({}); + return; + } - if (process.platform === "win32") { - callback({ video, audio: "loopback" }); - return; - } + if (process.platform === "win32") { + callback({ video, audio: "loopback" }); + return; + } - callback({ video }); - }, - ); + callback({ video }); + }); } function registerIpc() { @@ -163,26 +144,16 @@ function registerIpc() { ipcMain.handle(ipcChannels.listScreenShareSources, listScreenShareSources); ipcMain.handle(ipcChannels.listScreenShareAudioOutputs, listAudioOutputs); - ipcMain.handle( - ipcChannels.getScreenShareCapabilities, - getScreenShareCapabilities, - ); - ipcMain.handle( - ipcChannels.selectScreenShareSource, - (_event, sourceId: unknown) => { - if ( - typeof sourceId !== "string" || - sourceId.length === 0 || - sourceId.length > 256 - ) { - throw new Error("Invalid screen share source id."); - } + ipcMain.handle(ipcChannels.getScreenShareCapabilities, getScreenShareCapabilities); + ipcMain.handle(ipcChannels.selectScreenShareSource, (_event, sourceId: unknown) => { + if (typeof sourceId !== "string" || sourceId.length === 0 || sourceId.length > 256) { + throw new Error("Invalid screen share source id."); + } - selectedScreenShareSourceId = sourceId; - verboseLog("selected screen share source", sourceId); - return true; - }, - ); + selectedScreenShareSourceId = sourceId; + verboseLog("selected screen share source", sourceId); + return true; + }); ipcMain.handle(ipcChannels.getVersion, () => app.getVersion()); ipcMain.handle(ipcChannels.checkForUpdates, checkForUpdates); ipcMain.handle(ipcChannels.minimizeWindow, () => { @@ -246,24 +217,18 @@ async function createWindow() { }); if (verbose) { - mainWindow.webContents.on( - "console-message", - (_event, level, message, line, sourceId) => { - const target = level >= 2 ? console.error : console.log; - target("[tensamin:renderer]", message, { level, line, sourceId }); - }, - ); + mainWindow.webContents.on("console-message", (_event, level, message, line, sourceId) => { + const target = level >= 2 ? console.error : console.log; + target("[tensamin:renderer]", message, { level, line, sourceId }); + }); - mainWindow.webContents.on( - "did-fail-load", - (_event, errorCode, errorDescription, validatedURL) => { - console.error("[tensamin:electron] renderer failed to load", { - errorCode, - errorDescription, - validatedURL, - }); - }, - ); + mainWindow.webContents.on("did-fail-load", (_event, errorCode, errorDescription, validatedURL) => { + console.error("[tensamin:electron] renderer failed to load", { + errorCode, + errorDescription, + validatedURL, + }); + }); mainWindow.webContents.on("did-finish-load", () => { verboseLog("renderer finished loading", mainWindow?.webContents.getURL()); diff --git a/apps/electron/src/main/updates.ts b/apps/electron/src/main/updates.ts index b5f7376..7386118 100644 --- a/apps/electron/src/main/updates.ts +++ b/apps/electron/src/main/updates.ts @@ -3,21 +3,13 @@ import { createReadStream } from "node:fs"; import { mkdir, rm, writeFile } from "node:fs/promises"; import { basename, join } from "node:path"; import { app, net } from "electron"; -import type { - ReleaseArtifact, - ReleaseMetadata, - UpdateCheckResult, -} from "../shared/ipc.js"; +import type { ReleaseArtifact, ReleaseMetadata, UpdateCheckResult } from "../shared/ipc.js"; const metadataUrl = process.env.TENSAMIN_UPDATE_METADATA_URL; function compareSemver(left: string, right: string) { - const leftParts = left - .split(/[.-]/) - .map((part) => Number.parseInt(part, 10) || 0); - const rightParts = right - .split(/[.-]/) - .map((part) => Number.parseInt(part, 10) || 0); + const leftParts = left.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0); + const rightParts = right.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0); const length = Math.max(leftParts.length, rightParts.length); for (let index = 0; index < length; index += 1) { @@ -50,9 +42,7 @@ function requestText(url: string): Promise { const request = net.request(url); request.on("response", (response) => { if (response.statusCode < 200 || response.statusCode >= 300) { - reject( - new Error(`Update request failed with HTTP ${response.statusCode}.`), - ); + reject(new Error(`Update request failed with HTTP ${response.statusCode}.`)); return; } @@ -96,9 +86,7 @@ async function sha256File(filePath: string) { return hash.digest("hex"); } -function selectArtifact( - metadata: ReleaseMetadata, -): ReleaseArtifact | undefined { +function selectArtifact(metadata: ReleaseMetadata): ReleaseArtifact | undefined { const platform = platformName(); const arch = archName(); @@ -114,26 +102,16 @@ export async function checkForUpdates(): Promise { return { available: false, currentVersion, latestVersion: currentVersion }; } - const metadata = JSON.parse( - await requestText(metadataUrl), - ) as ReleaseMetadata; + const metadata = JSON.parse(await requestText(metadataUrl)) as ReleaseMetadata; const artifact = selectArtifact(metadata); if (isDevVersion(metadata.version) !== isDevVersion(currentVersion)) { - return { - available: false, - currentVersion, - latestVersion: metadata.version, - }; + return { available: false, currentVersion, latestVersion: metadata.version }; } if (isDevVersion(currentVersion)) { if (!artifact || metadata.version === currentVersion) { - return { - available: false, - currentVersion, - latestVersion: metadata.version, - }; + return { available: false, currentVersion, latestVersion: metadata.version }; } return { @@ -145,11 +123,7 @@ export async function checkForUpdates(): Promise { } if (!artifact || compareSemver(metadata.version, currentVersion) <= 0) { - return { - available: false, - currentVersion, - latestVersion: metadata.version, - }; + return { available: false, currentVersion, latestVersion: metadata.version }; } return { @@ -166,9 +140,7 @@ export async function downloadVerifiedArtifact(artifact: ReleaseArtifact) { await mkdir(updatesDir, { recursive: true }); const destination = join(updatesDir, basename(artifact.name)); - await writeFile(destination, await requestBuffer(artifact.url), { - mode: 0o600, - }); + await writeFile(destination, await requestBuffer(artifact.url), { mode: 0o600 }); const actualHash = await sha256File(destination); if (actualHash !== artifact.sha256) { diff --git a/apps/tauri/logo.json b/apps/tauri/logo.json index 4c85e3b..9cd2fcb 100644 --- a/apps/tauri/logo.json +++ b/apps/tauri/logo.json @@ -5,4 +5,4 @@ "android_bg": "./background.png", "android_fg_scale": 100, "android_monochrome": "./monochrome.png" -} +} \ No newline at end of file diff --git a/apps/tauri/scripts/sync-electron-icons.ts b/apps/tauri/scripts/sync-electron-icons.ts index 62f232b..db5cc76 100644 --- a/apps/tauri/scripts/sync-electron-icons.ts +++ b/apps/tauri/scripts/sync-electron-icons.ts @@ -8,24 +8,12 @@ const clientDir = resolve(tauriDir, "../.."); const tauriIconsDir = join(tauriDir, "src-tauri", "icons"); const electronIconsDir = join(clientDir, "apps", "electron", "build", "icons"); -const desktopIcons = [ - "32x32.png", - "64x64.png", - "128x128.png", - "128x128@2x.png", - "icon.png", - "icon.ico", - "icon.icns", -]; +const desktopIcons = ["32x32.png", "64x64.png", "128x128.png", "128x128@2x.png", "icon.png", "icon.ico", "icon.icns"]; await mkdir(electronIconsDir, { recursive: true }); await Promise.all( - desktopIcons.map((icon) => - copyFile(join(tauriIconsDir, icon), join(electronIconsDir, icon)), - ), + desktopIcons.map((icon) => copyFile(join(tauriIconsDir, icon), join(electronIconsDir, icon))), ); -console.log( - `Synced ${desktopIcons.length} desktop icons to ${electronIconsDir}`, -); +console.log(`Synced ${desktopIcons.length} desktop icons to ${electronIconsDir}`); diff --git a/eslint.config.ts b/eslint.config.ts index 19d24ae..2209bf9 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -10,7 +10,7 @@ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url)); export default [ { - ignores: ["**/dist/**", "**/node_modules/**"], + ignores: ["**/dist/**", "**/node_modules/**", "**/.tmp/**"], }, js.configs.recommended, ...tseslint.configs.recommended, diff --git a/packages/shared/src/desktopMedia.tsx b/packages/shared/src/desktopMedia.tsx index 4da0119..d117716 100644 --- a/packages/shared/src/desktopMedia.tsx +++ b/packages/shared/src/desktopMedia.tsx @@ -26,9 +26,7 @@ type ElectronDesktopApi = { media?: { getScreenShareCapabilities?: () => Promise; listScreenShareSources?: () => Promise; - listScreenShareAudioOutputs?: () => Promise< - DesktopScreenShareAudioOutput[] - >; + listScreenShareAudioOutputs?: () => Promise; selectScreenShareSource?: (sourceId: string) => Promise; }; }; diff --git a/scripts/copy-releases.ts b/scripts/copy-releases.ts index 194228a..3b0eeec 100644 --- a/scripts/copy-releases.ts +++ b/scripts/copy-releases.ts @@ -60,9 +60,7 @@ const electronReleaseDir = "apps/electron/release"; const copyElectronArtifacts = () => { if (!existsSync(electronReleaseDir)) { - console.warn( - `Skipping Electron artifacts: ${electronReleaseDir} does not exist.`, - ); + console.warn(`Skipping Electron artifacts: ${electronReleaseDir} does not exist.`); return; } @@ -83,11 +81,7 @@ const artifacts = await Promise.all( readdirSync(releasesDir) .map((file) => join(releasesDir, file)) .filter((file) => statSync(file).isFile()) - .filter( - (file) => - !file.endsWith("electron-release-metadata.json") && - !file.endsWith("SHA256SUMS"), - ) + .filter((file) => !file.endsWith("electron-release-metadata.json") && !file.endsWith("SHA256SUMS")) .map(async (filePath) => { const name = filePath.split(/[\\/]/).at(-1)!; diff --git a/tsconfig.json b/tsconfig.json index 5fdf2a0..0096dc3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2020", "module": "nodenext", "lib": ["ES2020"], - "rootDir": ".", + "rootDir": "./scripts", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -12,6 +12,6 @@ "moduleResolution": "nodenext", "types": ["node"] }, - "include": ["scripts", "eslint.config.ts"], + "include": ["scripts"], "exclude": ["node_modules", "dist"] }