(qol): format
This commit is contained in:
parent
4445852c42
commit
a82ee292ce
11 changed files with 171 additions and 69 deletions
|
|
@ -5,4 +5,5 @@ coverage
|
|||
*.tsbuildinfo
|
||||
bun.lock
|
||||
apps/tauri/src-tauri
|
||||
apps/electron/release
|
||||
licenses
|
||||
|
|
|
|||
|
|
@ -60,7 +60,11 @@
|
|||
}
|
||||
],
|
||||
"linux": {
|
||||
"target": ["AppImage", "deb", "rpm"],
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb",
|
||||
"rpm"
|
||||
],
|
||||
"icon": "build/icons",
|
||||
"executableName": "tensamin",
|
||||
"category": "Network",
|
||||
|
|
@ -73,11 +77,16 @@
|
|||
}
|
||||
},
|
||||
"win": {
|
||||
"target": ["nsis", "portable"],
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
],
|
||||
"icon": "build/icons/icon.ico"
|
||||
},
|
||||
"mac": {
|
||||
"target": ["dmg"],
|
||||
"target": [
|
||||
"dmg"
|
||||
],
|
||||
"icon": "build/icons/icon.icns"
|
||||
},
|
||||
"publish": null
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
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" };
|
||||
|
||||
|
|
@ -55,7 +61,10 @@ 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`,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
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");
|
||||
|
|
@ -16,7 +26,11 @@ 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");
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +101,8 @@ async function listAudioOutputs() {
|
|||
if (!sink || typeof sink !== "object") return null;
|
||||
const record = sink as Record<string, unknown>;
|
||||
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 };
|
||||
})
|
||||
|
|
@ -113,30 +128,34 @@ 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() {
|
||||
|
|
@ -144,16 +163,26 @@ 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, () => {
|
||||
|
|
@ -217,18 +246,24 @@ 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());
|
||||
|
|
|
|||
|
|
@ -3,13 +3,21 @@ 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) {
|
||||
|
|
@ -42,7 +50,9 @@ function requestText(url: string): Promise<string> {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +96,9 @@ 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();
|
||||
|
||||
|
|
@ -102,16 +114,26 @@ export async function checkForUpdates(): Promise<UpdateCheckResult> {
|
|||
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 {
|
||||
|
|
@ -123,7 +145,11 @@ export async function checkForUpdates(): Promise<UpdateCheckResult> {
|
|||
}
|
||||
|
||||
if (!artifact || compareSemver(metadata.version, currentVersion) <= 0) {
|
||||
return { available: false, currentVersion, latestVersion: metadata.version };
|
||||
return {
|
||||
available: false,
|
||||
currentVersion,
|
||||
latestVersion: metadata.version,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -140,7 +166,9 @@ 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) {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,24 @@ 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}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
|
|||
|
||||
export default [
|
||||
{
|
||||
ignores: ["**/dist/**", "**/node_modules/**", "**/.tmp/**"],
|
||||
ignores: ["**/dist/**", "**/node_modules/**"],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ type ElectronDesktopApi = {
|
|||
media?: {
|
||||
getScreenShareCapabilities?: () => Promise<DesktopScreenShareCapabilities>;
|
||||
listScreenShareSources?: () => Promise<DesktopScreenShareSource[]>;
|
||||
listScreenShareAudioOutputs?: () => Promise<DesktopScreenShareAudioOutput[]>;
|
||||
listScreenShareAudioOutputs?: () => Promise<
|
||||
DesktopScreenShareAudioOutput[]
|
||||
>;
|
||||
selectScreenShareSource?: (sourceId: string) => Promise<boolean>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +83,11 @@ 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)!;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"target": "ES2020",
|
||||
"module": "nodenext",
|
||||
"lib": ["ES2020"],
|
||||
"rootDir": "./scripts",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
|
|
@ -12,6 +12,6 @@
|
|||
"moduleResolution": "nodenext",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["scripts"],
|
||||
"include": ["scripts", "eslint.config.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue