JM Tech Power

jm-tech-power-official/Flash Download

A desktop download manager that opens up to 32 connections against one file and can send each of them out a different proxy route.

Public Uncategorized
Clone jm-tech-power-official/flash-download.jmproj
src/main/main.js
'use strict';

const { app, BrowserWindow, ipcMain, dialog, shell, clipboard, Menu, safeStorage } = require('electron');
const path = require('path');
const { Manager } = require('./engine/manager');
const { SecretStore, electronCodec } = require('./engine/secrets');
const { probe, classify } = require('./engine/net');

const isMac = process.platform === 'darwin';
let win = null;
let manager = null;
let clipboardTimer = null;
let lastClipboard = '';
let pendingUrl = null;

// Only one copy may own the queue and the state file. A second launch — from a
// browser handoff, the launcher, or `flash-download <url>` — hands its argument
// to the running window instead of starting a rival downloader.
// (`return` is legal at the top level of a CommonJS module.)
if (!app.requestSingleInstanceLock()) {
  app.quit();
  return;
}

const urlFrom = (argv) => (argv || []).find((a) => /^https?:\/\/\S+$/i.test(a)) || null;

/** Offer a link to the window, or hold it until the window can receive it. */
function offerUrl(url) {
  if (!url) return;
  if (win && !win.isDestroyed() && !win.webContents.isLoading()) {
    win.webContents.send('open-url', url);
    if (win.isMinimized()) win.restore();
    win.focus();
  } else {
    pendingUrl = url;
  }
}

app.on('second-instance', (_event, argv) => {
  if (win && !win.isDestroyed()) {
    if (win.isMinimized()) win.restore();
    win.show();
    win.focus();
  }
  offerUrl(urlFrom(argv));
});

// macOS hands links over this way rather than on argv.
app.on('open-url', (event, url) => { event.preventDefault(); offerUrl(url); });

function createWindow() {
  win = new BrowserWindow({
    width: 1180,
    height: 760,
    minWidth: 720,
    minHeight: 480,
    show: false,
    backgroundColor: '#0C121C',
    // One custom 38px titlebar on every platform; only the control cluster differs.
    frame: false,
    titleBarStyle: isMac ? 'hiddenInset' : 'hidden',
    trafficLightPosition: isMac ? { x: 14, y: 11 } : undefined,
    webPreferences: {
      preload: path.join(__dirname, '..', 'preload', 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true,
      spellcheck: false,
    },
  });

  win.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'));
  win.once('ready-to-show', () => win.show());
  win.on('closed', () => { win = null; });
  win.webContents.on('did-finish-load', () => {
    const url = pendingUrl;
    pendingUrl = null;
    if (url) win.webContents.send('open-url', url);
  });

  // There is no menu bar to carry these, so the window handles them itself.
  win.webContents.on('before-input-event', (event, input) => {
    if (input.type !== 'keyDown') return;
    const mod = isMac ? input.meta : input.control;
    if (!mod || input.alt) return;
    const key = (input.key || '').toLowerCase();
    if (key === 'q') { event.preventDefault(); app.quit(); }
    else if (key === 'w') { event.preventDefault(); win.close(); }
  });

  const push = () => {
    if (!win || win.isDestroyed()) return;
    win.webContents.send('win:state', { maximized: win.isMaximized(), platform: process.platform });
  };
  win.on('maximize', push);
  win.on('unmaximize', push);
}

function send(channel, payload) {
  if (win && !win.isDestroyed()) win.webContents.send(channel, payload);
}

/** Watch the clipboard for links and offer them in the capture bar — never auto-start. */
function watchClipboard() {
  clearInterval(clipboardTimer);
  clipboardTimer = setInterval(() => {
    if (!manager || !manager.settings.captureClipboard) return;
    const text = (clipboard.readText() || '').trim();
    if (!text || text === lastClipboard || text.length > 2048) return;
    lastClipboard = text;
    if (!/^https?:\/\/\S+$/i.test(text)) return;
    if (manager.downloads.some((d) => d.originUrl === text)) return;
    send('clipboard:url', text);
  }, 1000);
  if (clipboardTimer.unref) clipboardTimer.unref();
}

app.whenReady().then(async () => {
  // safeStorage is only valid after the app is ready. Its key is held by the OS
  // (DPAPI / Keychain / libsecret); the encrypted blobs live beside state.json.
  const userData = app.getPath('userData');
  const secrets = new SecretStore({
    filePath: path.join(userData, 'credentials.enc'),
    codec: electronCodec(safeStorage),
  });

  manager = new Manager({
    stateDir: userData,
    defaultDownloadDir: app.getPath('downloads'),
    secrets,
  });
  await manager.load();
  manager.on('change', (state) => send('state', state));
  manager.on('notice', (notice) => send('notice', notice));

  createWindow();
  watchClipboard();
  offerUrl(urlFrom(process.argv));

  // A pool with unknown latency is not much of a pool — measure it once the
  // window is up, rather than making the user press Test all every launch.
  setTimeout(() => manager.testAllRoutes().catch(() => {}), 1200);
  if (isMac) Menu.setApplicationMenu(Menu.buildFromTemplate([{ role: 'appMenu' }, { role: 'editMenu' }, { role: 'windowMenu' }]));
  else Menu.setApplicationMenu(null);

  app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
});

app.on('window-all-closed', async () => {
  if (isMac) return;
  if (manager && !manager.closed) {
    manager.closed = true;
    await manager.shutdown();
  }
  app.quit();
});

app.on('before-quit', async (event) => {
  if (!manager || manager.closed) return;
  event.preventDefault();
  manager.closed = true;
  await manager.shutdown();
  app.quit();
});

// ---- IPC --------------------------------------------------------------------

const ok = (value) => ({ ok: true, value });
const fail = (err) => ({ ok: false, error: classify(err).message });

const handle = (channel, fn) => ipcMain.handle(channel, async (_event, ...args) => {
  try { return ok(await fn(...args)); } catch (err) { return fail(err); }
});

handle('state:get', () => manager.snapshot());

handle('url:probe', async (url) => {
  const route = manager.routes.find((r) => r.id === manager.settings.routeMode) || null;
  const info = await probe(url, { route: route && route.type !== 'direct' ? route : null });
  return info;
});

handle('download:add', (opts) => manager.add(opts));
handle('download:pause', (id) => manager.pause(id));
handle('download:resume', (id) => manager.resume(id));
handle('download:retry', (id) => manager.retry(id));
handle('download:rebalance', (id) => manager.rebalance(id));
handle('download:cancel', (id) => manager.cancel(id));
handle('download:remove', (id, deleteFile) => manager.remove(id, { deleteFile }));
handle('download:clearFinished', () => manager.clearFinished());

handle('download:reveal', (id) => {
  const dl = manager.find(id);
  if (dl && dl.filePath) shell.showItemInFolder(dl.filePath);
});
handle('download:open', async (id) => {
  const dl = manager.find(id);
  if (dl && dl.filePath && dl.state === 'done') return shell.openPath(dl.filePath);
});

handle('route:add', (route) => manager.addRoute(route));
handle('route:update', (id, patch) => manager.updateRoute(id, patch));
handle('route:remove', (id) => manager.removeRoute(id));
handle('route:test', (id) => manager.testRoute(id));
handle('route:testAll', () => manager.testAllRoutes());

handle('settings:update', (patch) => manager.updateSettings(patch));

handle('dialog:chooseFolder', async (current) => {
  const result = await dialog.showOpenDialog(win, {
    properties: ['openDirectory', 'createDirectory'],
    defaultPath: current || manager.settings.downloadDir,
  });
  return result.canceled ? null : result.filePaths[0];
});

handle('win:minimize', () => win && win.minimize());
handle('win:maximize', () => {
  if (!win) return;
  win.isMaximized() ? win.unmaximize() : win.maximize();
});
handle('win:close', () => win && win.close());
handle('win:info', () => ({ platform: process.platform, maximized: win ? win.isMaximized() : false }));