Merge pull request #535 from tw93/dev
merge: Cli support inject js/css files to app and set app safe domains - Inject css/js files to app - Support setting app safeDomains - More comfortable development mode for CLI power users
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -8,3 +8,4 @@ script/* linguist-vendored
|
|||||||
/icns2png.py linguist-vendored
|
/icns2png.py linguist-vendored
|
||||||
/rollup.config.js linguist-vendored
|
/rollup.config.js linguist-vendored
|
||||||
src-tauri/src/inject/* linguist-vendored
|
src-tauri/src/inject/* linguist-vendored
|
||||||
|
src-tauri/src/.pake/* linguist-vendored
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra
|
packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra
|
||||||
version: 1.0
|
version: 1.0
|
||||||
- name: Run unit & integration tests with nextest
|
- name: Run unit & integration tests with nextest
|
||||||
run: cargo hack --feature-powerset nextest run
|
run: cargo hack --feature-powerset --exclude-features cli-build nextest run
|
||||||
# - name: Run documentation tests with cargo test
|
# - name: Run documentation tests with cargo test
|
||||||
# run: cargo hack --feature-powerset test --doc
|
# run: cargo hack --feature-powerset test --doc
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra
|
packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra
|
||||||
version: 1.0
|
version: 1.0
|
||||||
- name: Run all-features code quality checks
|
- name: Run all-features code quality checks
|
||||||
run: cargo hack --feature-powerset --no-dev-deps clippy
|
run: cargo hack --feature-powerset --exclude-features cli-build --no-dev-deps clippy
|
||||||
- name: Run normal code quality check
|
- name: Run normal code quality check
|
||||||
run: cargo clippy
|
run: cargo clippy
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,3 +37,4 @@ dist
|
|||||||
src-tauri/.cargo/config
|
src-tauri/.cargo/config
|
||||||
src-tauri/.cargo/
|
src-tauri/.cargo/
|
||||||
.next
|
.next
|
||||||
|
src-tauri/.pake/
|
||||||
7
bin/builders/BaseBuilder.ts
vendored
7
bin/builders/BaseBuilder.ts
vendored
@@ -71,6 +71,10 @@ export default abstract class BaseBuilder {
|
|||||||
await this.buildAndCopy(url, this.options.targets);
|
await this.buildAndCopy(url, this.options.targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async start(url: string) {
|
||||||
|
await mergeConfig(url, this.options, tauriConfig);
|
||||||
|
}
|
||||||
|
|
||||||
async buildAndCopy(url: string, target: string) {
|
async buildAndCopy(url: string, target: string) {
|
||||||
const { name } = this.options;
|
const { name } = this.options;
|
||||||
await mergeConfig(url, this.options, tauriConfig);
|
await mergeConfig(url, this.options, tauriConfig);
|
||||||
@@ -98,7 +102,8 @@ export default abstract class BaseBuilder {
|
|||||||
abstract getFileName(): string;
|
abstract getFileName(): string;
|
||||||
|
|
||||||
protected getBuildCommand(): string {
|
protected getBuildCommand(): string {
|
||||||
return 'npm run build';
|
// the debug option should support `--debug` and `--release`
|
||||||
|
return this.options.debug ? 'npm run build:debug' : 'npm run build';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getBasePath(): string {
|
protected getBasePath(): string {
|
||||||
|
|||||||
2
bin/cli.ts
vendored
2
bin/cli.ts
vendored
@@ -38,6 +38,8 @@ program
|
|||||||
.option('--iter-copy-file', 'Copy files when URL is a local file', DEFAULT.iterCopyFile)
|
.option('--iter-copy-file', 'Copy files when URL is a local file', DEFAULT.iterCopyFile)
|
||||||
.option('--multi-arch', 'Only for Mac, supports both Intel and M1', DEFAULT.multiArch)
|
.option('--multi-arch', 'Only for Mac, supports both Intel and M1', DEFAULT.multiArch)
|
||||||
.option('--targets <string>', 'Only for Linux, option "deb" or "appimage"', DEFAULT.targets)
|
.option('--targets <string>', 'Only for Linux, option "deb" or "appimage"', DEFAULT.targets)
|
||||||
|
.option('--inject [injects...]', 'Inject .js or .css for this app', DEFAULT.inject)
|
||||||
|
.option('--safe-domain [domains...]', 'Please enter the security domains that need to be configured', DEFAULT.safeDomain)
|
||||||
.option('--debug', 'Debug mode', DEFAULT.debug)
|
.option('--debug', 'Debug mode', DEFAULT.debug)
|
||||||
.version(packageJson.version, '-v, --version', 'Output the current version')
|
.version(packageJson.version, '-v, --version', 'Output the current version')
|
||||||
.action(async (url: string, options: PakeCliOptions) => {
|
.action(async (url: string, options: PakeCliOptions) => {
|
||||||
|
|||||||
9
bin/defaults.ts
vendored
9
bin/defaults.ts
vendored
@@ -15,4 +15,13 @@ export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = {
|
|||||||
iterCopyFile: false,
|
iterCopyFile: false,
|
||||||
systemTrayIcon: '',
|
systemTrayIcon: '',
|
||||||
debug: false,
|
debug: false,
|
||||||
|
inject: [],
|
||||||
|
safeDomain: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// just for cli development
|
||||||
|
export const DEFAULT_DEV_PAKE_OPTIONS: PakeCliOptions & {url: string} = {
|
||||||
|
...DEFAULT_PAKE_OPTIONS,
|
||||||
|
url: 'https://weread.qq.com',
|
||||||
|
name: 'Weread',
|
||||||
|
}
|
||||||
17
bin/dev.ts
vendored
Normal file
17
bin/dev.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import log from 'loglevel';
|
||||||
|
import {DEFAULT_DEV_PAKE_OPTIONS} from './defaults';
|
||||||
|
import handleInputOptions from './options/index';
|
||||||
|
import BuilderProvider from './builders/BuilderProvider';
|
||||||
|
|
||||||
|
async function startBuild() {
|
||||||
|
log.setDefaultLevel('debug');
|
||||||
|
|
||||||
|
const appOptions = await handleInputOptions(DEFAULT_DEV_PAKE_OPTIONS, DEFAULT_DEV_PAKE_OPTIONS.url);
|
||||||
|
log.debug('PakeAppOptions', appOptions);
|
||||||
|
|
||||||
|
const builder = BuilderProvider.create(appOptions);
|
||||||
|
await builder.prepare();
|
||||||
|
await builder.start(DEFAULT_DEV_PAKE_OPTIONS.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
startBuild();
|
||||||
75
bin/helpers/merge.ts
vendored
75
bin/helpers/merge.ts
vendored
@@ -2,8 +2,10 @@ import path from 'path';
|
|||||||
import fsExtra from 'fs-extra';
|
import fsExtra from 'fs-extra';
|
||||||
|
|
||||||
import { npmDirectory } from '@/utils/dir';
|
import { npmDirectory } from '@/utils/dir';
|
||||||
|
import combineFiles from '@/utils/combine';
|
||||||
import logger from '@/options/logger';
|
import logger from '@/options/logger';
|
||||||
import { PakeAppOptions, PlatformMap } from '@/types';
|
import { PakeAppOptions, PlatformMap } from '@/types';
|
||||||
|
import { tauriConfigDirectory } from '../utils/dir';
|
||||||
|
|
||||||
export async function mergeConfig(url: string, options: PakeAppOptions, tauriConf: any) {
|
export async function mergeConfig(url: string, options: PakeAppOptions, tauriConf: any) {
|
||||||
const {
|
const {
|
||||||
@@ -19,6 +21,8 @@ export async function mergeConfig(url: string, options: PakeAppOptions, tauriCon
|
|||||||
identifier,
|
identifier,
|
||||||
name,
|
name,
|
||||||
resizable = true,
|
resizable = true,
|
||||||
|
inject,
|
||||||
|
safeDomain,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
@@ -57,9 +61,7 @@ export async function mergeConfig(url: string, options: PakeAppOptions, tauriCon
|
|||||||
|
|
||||||
const filesToCopyBack = ['cli.js', 'about_pake.html'];
|
const filesToCopyBack = ['cli.js', 'about_pake.html'];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
filesToCopyBack.map(file =>
|
filesToCopyBack.map(file => fsExtra.copy(path.join(distBakDir, file), path.join(distDir, file))),
|
||||||
fsExtra.copy(path.join(distBakDir, file), path.join(distDir, file)),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +70,24 @@ export async function mergeConfig(url: string, options: PakeAppOptions, tauriCon
|
|||||||
} else {
|
} else {
|
||||||
tauriConf.pake.windows[0].url_type = 'web';
|
tauriConf.pake.windows[0].url_type = 'web';
|
||||||
// Set the secure domain for calling window.__TAURI__ to the application domain that has been set.
|
// Set the secure domain for calling window.__TAURI__ to the application domain that has been set.
|
||||||
tauriConf.tauri.security.dangerousRemoteDomainIpcAccess[0].domain = new URL(url).hostname;
|
tauriConf.tauri.security.dangerousRemoteDomainIpcAccess = [
|
||||||
|
{
|
||||||
|
domain: new URL(url).hostname,
|
||||||
|
windows: ['pake'],
|
||||||
|
enableTauriAPI: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (safeDomain.length > 0) {
|
||||||
|
tauriConf.tauri.security.dangerousRemoteDomainIpcAccess = [
|
||||||
|
...tauriConf.tauri.security.dangerousRemoteDomainIpcAccess,
|
||||||
|
...safeDomain.map(domain => ({
|
||||||
|
domain,
|
||||||
|
windows: ['pake'],
|
||||||
|
enableTauriAPI: true,
|
||||||
|
})),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const platformMap: PlatformMap = {
|
const platformMap: PlatformMap = {
|
||||||
@@ -90,12 +109,9 @@ export async function mergeConfig(url: string, options: PakeAppOptions, tauriCon
|
|||||||
delete tauriConf.tauri.bundle.deb.files;
|
delete tauriConf.tauri.bundle.deb.files;
|
||||||
const validTargets = ['all', 'deb', 'appimage'];
|
const validTargets = ['all', 'deb', 'appimage'];
|
||||||
if (validTargets.includes(options.targets)) {
|
if (validTargets.includes(options.targets)) {
|
||||||
tauriConf.tauri.bundle.targets =
|
tauriConf.tauri.bundle.targets = options.targets === 'all' ? ['deb', 'appimage'] : [options.targets];
|
||||||
options.targets === 'all' ? ['deb', 'appimage'] : [options.targets];
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn(`✼ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`);
|
||||||
`✼ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +170,7 @@ export async function mergeConfig(url: string, options: PakeAppOptions, tauriCon
|
|||||||
// 需要判断图标格式,默认只支持ico和png两种
|
// 需要判断图标格式,默认只支持ico和png两种
|
||||||
let iconExt = path.extname(systemTrayIcon).toLowerCase();
|
let iconExt = path.extname(systemTrayIcon).toLowerCase();
|
||||||
if (iconExt == '.png' || iconExt == '.ico') {
|
if (iconExt == '.png' || iconExt == '.ico') {
|
||||||
const trayIcoPath = path.join(
|
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}${iconExt}`);
|
||||||
npmDirectory,
|
|
||||||
`src-tauri/png/${name.toLowerCase()}${iconExt}`,
|
|
||||||
);
|
|
||||||
trayIconPath = `png/${name.toLowerCase()}${iconExt}`;
|
trayIconPath = `png/${name.toLowerCase()}${iconExt}`;
|
||||||
await fsExtra.copy(systemTrayIcon, trayIcoPath);
|
await fsExtra.copy(systemTrayIcon, trayIcoPath);
|
||||||
} else {
|
} else {
|
||||||
@@ -171,24 +184,40 @@ export async function mergeConfig(url: string, options: PakeAppOptions, tauriCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
tauriConf.tauri.systemTray.iconPath = trayIconPath;
|
tauriConf.tauri.systemTray.iconPath = trayIconPath;
|
||||||
|
const injectFilePath = path.join(npmDirectory, `src-tauri/src/inject/custom.js`);
|
||||||
|
// inject js or css files
|
||||||
|
if (inject?.length > 0) {
|
||||||
|
if (!inject.every(item => item.endsWith('.css') || item.endsWith('.js'))) {
|
||||||
|
logger.error('The injected file must be in either CSS or JS format.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const files = inject.map(filepath => path.isAbsolute(filepath) ? filepath : path.join(process.cwd(), filepath));
|
||||||
|
tauriConf.pake.inject = files;
|
||||||
|
await combineFiles(files, injectFilePath);
|
||||||
|
} else {
|
||||||
|
tauriConf.pake.inject = [];
|
||||||
|
await fsExtra.writeFile(injectFilePath, '');
|
||||||
|
}
|
||||||
|
|
||||||
// Save config file.
|
// Save config file.
|
||||||
const platformConfigPaths: PlatformMap = {
|
const platformConfigPaths: PlatformMap = {
|
||||||
win32: 'src-tauri/tauri.windows.conf.json',
|
win32: 'tauri.windows.conf.json',
|
||||||
darwin: 'src-tauri/tauri.macos.conf.json',
|
darwin: 'tauri.macos.conf.json',
|
||||||
linux: 'src-tauri/tauri.linux.conf.json',
|
linux: 'tauri.linux.conf.json',
|
||||||
};
|
};
|
||||||
const configPath = path.join(npmDirectory, platformConfigPaths[platform]);
|
const configPath = path.join(tauriConfigDirectory, platformConfigPaths[platform]);
|
||||||
|
|
||||||
const bundleConf = { tauri: { bundle: tauriConf.tauri.bundle } };
|
const bundleConf = { tauri: { bundle: tauriConf.tauri.bundle } };
|
||||||
await fsExtra.writeJson(configPath, bundleConf, { spaces: 4 });
|
await fsExtra.outputJSON(configPath, bundleConf, { spaces: 4 });
|
||||||
|
const pakeConfigPath = path.join(tauriConfigDirectory, 'pake.json');
|
||||||
const pakeConfigPath = path.join(npmDirectory, 'src-tauri/pake.json');
|
await fsExtra.outputJSON(pakeConfigPath, tauriConf.pake, { spaces: 4 });
|
||||||
await fsExtra.writeJson(pakeConfigPath, tauriConf.pake, { spaces: 4 });
|
|
||||||
|
|
||||||
let tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
let tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
||||||
delete tauriConf2.pake;
|
delete tauriConf2.pake;
|
||||||
delete tauriConf2.tauri.bundle;
|
delete tauriConf2.tauri.bundle;
|
||||||
const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json');
|
if (process.env.NODE_ENV === 'development') {
|
||||||
await fsExtra.writeJson(configJsonPath, tauriConf2, { spaces: 4 });
|
tauriConf2.tauri.bundle = bundleConf.tauri.bundle;
|
||||||
|
}
|
||||||
|
const configJsonPath = path.join(tauriConfigDirectory, 'tauri.conf.json');
|
||||||
|
await fsExtra.outputJSON(configJsonPath, tauriConf2, { spaces: 4 });
|
||||||
}
|
}
|
||||||
|
|||||||
6
bin/types.ts
vendored
6
bin/types.ts
vendored
@@ -47,6 +47,12 @@ export interface PakeCliOptions {
|
|||||||
|
|
||||||
// Debug mode, outputs more logs
|
// Debug mode, outputs more logs
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
|
|
||||||
|
/** 需要注入页面的外部脚本 */
|
||||||
|
inject: string[];
|
||||||
|
|
||||||
|
/* the domain that can use ipc or tauri javascript sdk */
|
||||||
|
safeDomain: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PakeAppOptions extends PakeCliOptions {
|
export interface PakeAppOptions extends PakeCliOptions {
|
||||||
|
|||||||
14
bin/utils/combine.ts
vendored
Normal file
14
bin/utils/combine.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
export default async function combineFiles(files: string[], output: string) {
|
||||||
|
const contents = files.map(file => {
|
||||||
|
const fileContent = fs.readFileSync(file);
|
||||||
|
if (file.endsWith('.css')) {
|
||||||
|
return "window.addEventListener('DOMContentLoaded', (_event) => { const css = `" + fileContent + "`; const style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); });";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "window.addEventListener('DOMContentLoaded', (_event) => { " + fileContent + " });";
|
||||||
|
});
|
||||||
|
fs.writeFileSync(output, contents.join('\n'));
|
||||||
|
return files;
|
||||||
|
}
|
||||||
2
bin/utils/dir.ts
vendored
2
bin/utils/dir.ts
vendored
@@ -6,3 +6,5 @@ const currentModulePath = fileURLToPath(import.meta.url);
|
|||||||
|
|
||||||
// Resolve the parent directory of the current module
|
// Resolve the parent directory of the current module
|
||||||
export const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
export const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
||||||
|
|
||||||
|
export const tauriConfigDirectory = process.env.NODE_ENV === 'development' ? path.join(npmDirectory, 'src-tauri', '.pake') : path.join(npmDirectory, 'src-tauri');
|
||||||
0
dist/.gitkeep
vendored
0
dist/.gitkeep
vendored
16
dist/about_pake.html
vendored
16
dist/about_pake.html
vendored
@@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h5>Welcome from Pake!</h5>
|
|
||||||
<p>version: 1.0.9</p>
|
|
||||||
<a href="https://github.com/tw93/Pake">Project link</a><br>
|
|
||||||
<a href="https://github.com/tw93/Pake/discussions">Discussions</a><br>
|
|
||||||
<a href="https://github.com/tw93/Pake/issues" >Issues</a><br>
|
|
||||||
<p>LICENSE: MIT</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
79
dist/cli.js
vendored
79
dist/cli.js
vendored
@@ -358,6 +358,7 @@ const IS_LINUX = platform$1 === 'linux';
|
|||||||
const currentModulePath = fileURLToPath(import.meta.url);
|
const currentModulePath = fileURLToPath(import.meta.url);
|
||||||
// Resolve the parent directory of the current module
|
// Resolve the parent directory of the current module
|
||||||
const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
||||||
|
const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri');
|
||||||
|
|
||||||
function shellExec(command) {
|
function shellExec(command) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -457,8 +458,20 @@ function checkRustInstalled() {
|
|||||||
return shelljs.exec('rustc --version', { silent: true }).code === 0;
|
return shelljs.exec('rustc --version', { silent: true }).code === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function combineFiles(files, output) {
|
||||||
|
const contents = files.map(file => {
|
||||||
|
const fileContent = fs.readFileSync(file);
|
||||||
|
if (file.endsWith('.css')) {
|
||||||
|
return "window.addEventListener('DOMContentLoaded', (_event) => { const css = `" + fileContent + "`; const style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); });";
|
||||||
|
}
|
||||||
|
return "window.addEventListener('DOMContentLoaded', (_event) => { " + fileContent + " });";
|
||||||
|
});
|
||||||
|
fs.writeFileSync(output, contents.join('\n'));
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
async function mergeConfig(url, options, tauriConf) {
|
async function mergeConfig(url, options, tauriConf) {
|
||||||
const { width, height, fullscreen, transparent, userAgent, showMenu, showSystemTray, systemTrayIcon, iterCopyFile, identifier, name, resizable = true, } = options;
|
const { width, height, fullscreen, transparent, userAgent, showMenu, showSystemTray, systemTrayIcon, iterCopyFile, identifier, name, resizable = true, inject, safeDomain, } = options;
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
// Set Windows parameters.
|
// Set Windows parameters.
|
||||||
const tauriConfWindowOptions = {
|
const tauriConfWindowOptions = {
|
||||||
@@ -496,7 +509,23 @@ async function mergeConfig(url, options, tauriConf) {
|
|||||||
else {
|
else {
|
||||||
tauriConf.pake.windows[0].url_type = 'web';
|
tauriConf.pake.windows[0].url_type = 'web';
|
||||||
// Set the secure domain for calling window.__TAURI__ to the application domain that has been set.
|
// Set the secure domain for calling window.__TAURI__ to the application domain that has been set.
|
||||||
tauriConf.tauri.security.dangerousRemoteDomainIpcAccess[0].domain = new URL(url).hostname;
|
tauriConf.tauri.security.dangerousRemoteDomainIpcAccess = [
|
||||||
|
{
|
||||||
|
domain: new URL(url).hostname,
|
||||||
|
windows: ['pake'],
|
||||||
|
enableTauriAPI: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (safeDomain.length > 0) {
|
||||||
|
tauriConf.tauri.security.dangerousRemoteDomainIpcAccess = [
|
||||||
|
...tauriConf.tauri.security.dangerousRemoteDomainIpcAccess,
|
||||||
|
...safeDomain.map(domain => ({
|
||||||
|
domain,
|
||||||
|
windows: ['pake'],
|
||||||
|
enableTauriAPI: true,
|
||||||
|
})),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
const platformMap = {
|
const platformMap = {
|
||||||
win32: 'windows',
|
win32: 'windows',
|
||||||
@@ -514,8 +543,7 @@ async function mergeConfig(url, options, tauriConf) {
|
|||||||
delete tauriConf.tauri.bundle.deb.files;
|
delete tauriConf.tauri.bundle.deb.files;
|
||||||
const validTargets = ['all', 'deb', 'appimage'];
|
const validTargets = ['all', 'deb', 'appimage'];
|
||||||
if (validTargets.includes(options.targets)) {
|
if (validTargets.includes(options.targets)) {
|
||||||
tauriConf.tauri.bundle.targets =
|
tauriConf.tauri.bundle.targets = options.targets === 'all' ? ['deb', 'appimage'] : [options.targets];
|
||||||
options.targets === 'all' ? ['deb', 'appimage'] : [options.targets];
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.warn(`✼ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`);
|
logger.warn(`✼ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`);
|
||||||
@@ -591,22 +619,37 @@ async function mergeConfig(url, options, tauriConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tauriConf.tauri.systemTray.iconPath = trayIconPath;
|
tauriConf.tauri.systemTray.iconPath = trayIconPath;
|
||||||
|
const injectFilePath = path.join(npmDirectory, `src-tauri/src/inject/_INJECT_.js`);
|
||||||
|
// inject js or css files
|
||||||
|
if (inject?.length > 0) {
|
||||||
|
if (!inject.every(item => item.endsWith('.css') || item.endsWith('.js'))) {
|
||||||
|
logger.error('The injected file must be in either CSS or JS format.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const files = inject.map(filepath => path.isAbsolute(filepath) ? filepath : path.join(process.cwd(), filepath));
|
||||||
|
tauriConf.pake.inject = files;
|
||||||
|
await combineFiles(files, injectFilePath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tauriConf.pake.inject = [];
|
||||||
|
await fsExtra.writeFile(injectFilePath, '');
|
||||||
|
}
|
||||||
// Save config file.
|
// Save config file.
|
||||||
const platformConfigPaths = {
|
const platformConfigPaths = {
|
||||||
win32: 'src-tauri/tauri.windows.conf.json',
|
win32: 'tauri.windows.conf.json',
|
||||||
darwin: 'src-tauri/tauri.macos.conf.json',
|
darwin: 'tauri.macos.conf.json',
|
||||||
linux: 'src-tauri/tauri.linux.conf.json',
|
linux: 'tauri.linux.conf.json',
|
||||||
};
|
};
|
||||||
const configPath = path.join(npmDirectory, platformConfigPaths[platform]);
|
const configPath = path.join(tauriConfigDirectory, platformConfigPaths[platform]);
|
||||||
const bundleConf = { tauri: { bundle: tauriConf.tauri.bundle } };
|
const bundleConf = { tauri: { bundle: tauriConf.tauri.bundle } };
|
||||||
await fsExtra.writeJson(configPath, bundleConf, { spaces: 4 });
|
await fsExtra.outputJSON(configPath, bundleConf, { spaces: 4 });
|
||||||
const pakeConfigPath = path.join(npmDirectory, 'src-tauri/pake.json');
|
const pakeConfigPath = path.join(tauriConfigDirectory, 'pake.json');
|
||||||
await fsExtra.writeJson(pakeConfigPath, tauriConf.pake, { spaces: 4 });
|
await fsExtra.outputJSON(pakeConfigPath, tauriConf.pake, { spaces: 4 });
|
||||||
let tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
let tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
||||||
delete tauriConf2.pake;
|
delete tauriConf2.pake;
|
||||||
delete tauriConf2.tauri.bundle;
|
delete tauriConf2.tauri.bundle;
|
||||||
const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json');
|
const configJsonPath = path.join(tauriConfigDirectory, 'tauri.conf.json');
|
||||||
await fsExtra.writeJson(configJsonPath, tauriConf2, { spaces: 4 });
|
await fsExtra.outputJSON(configJsonPath, tauriConf2, { spaces: 4 });
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseBuilder {
|
class BaseBuilder {
|
||||||
@@ -657,6 +700,9 @@ class BaseBuilder {
|
|||||||
async build(url) {
|
async build(url) {
|
||||||
await this.buildAndCopy(url, this.options.targets);
|
await this.buildAndCopy(url, this.options.targets);
|
||||||
}
|
}
|
||||||
|
async start(url) {
|
||||||
|
await mergeConfig(url, this.options, tauriConfig);
|
||||||
|
}
|
||||||
async buildAndCopy(url, target) {
|
async buildAndCopy(url, target) {
|
||||||
const { name } = this.options;
|
const { name } = this.options;
|
||||||
await mergeConfig(url, this.options, tauriConfig);
|
await mergeConfig(url, this.options, tauriConfig);
|
||||||
@@ -678,7 +724,8 @@ class BaseBuilder {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
getBuildCommand() {
|
getBuildCommand() {
|
||||||
return 'npm run build';
|
// the debug option should support `--debug` and `--release`
|
||||||
|
return this.options.debug ? 'npm run build:debug' : 'npm run build';
|
||||||
}
|
}
|
||||||
getBasePath() {
|
getBasePath() {
|
||||||
return 'src-tauri/target/release/bundle/';
|
return 'src-tauri/target/release/bundle/';
|
||||||
@@ -784,6 +831,8 @@ const DEFAULT_PAKE_OPTIONS = {
|
|||||||
iterCopyFile: false,
|
iterCopyFile: false,
|
||||||
systemTrayIcon: '',
|
systemTrayIcon: '',
|
||||||
debug: false,
|
debug: false,
|
||||||
|
inject: [],
|
||||||
|
safeDomain: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
async function checkUpdateTips() {
|
async function checkUpdateTips() {
|
||||||
@@ -974,6 +1023,8 @@ program
|
|||||||
.option('--iter-copy-file', 'Copy files when URL is a local file', DEFAULT_PAKE_OPTIONS.iterCopyFile)
|
.option('--iter-copy-file', 'Copy files when URL is a local file', DEFAULT_PAKE_OPTIONS.iterCopyFile)
|
||||||
.option('--multi-arch', 'Only for Mac, supports both Intel and M1', DEFAULT_PAKE_OPTIONS.multiArch)
|
.option('--multi-arch', 'Only for Mac, supports both Intel and M1', DEFAULT_PAKE_OPTIONS.multiArch)
|
||||||
.option('--targets <string>', 'Only for Linux, option "deb" or "appimage"', DEFAULT_PAKE_OPTIONS.targets)
|
.option('--targets <string>', 'Only for Linux, option "deb" or "appimage"', DEFAULT_PAKE_OPTIONS.targets)
|
||||||
|
.option('--inject [injects...]', 'inject .js or .css for this app', DEFAULT_PAKE_OPTIONS.inject)
|
||||||
|
.option('--safe-domain [domains...]', 'domains that can call window.__TAURI__ and use ipc', DEFAULT_PAKE_OPTIONS.safeDomain)
|
||||||
.option('--debug', 'Debug mode', DEFAULT_PAKE_OPTIONS.debug)
|
.option('--debug', 'Debug mode', DEFAULT_PAKE_OPTIONS.debug)
|
||||||
.version(packageJson.version, '-v, --version', 'Output the current version')
|
.version(packageJson.version, '-v, --version', 'Output the current version')
|
||||||
.action(async (url, options) => {
|
.action(async (url, options) => {
|
||||||
|
|||||||
@@ -33,12 +33,14 @@
|
|||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
"dev": "npm run tauri dev",
|
"dev": "npm run tauri dev",
|
||||||
"build": "npm run tauri build --release",
|
"build": "npm run tauri build --release",
|
||||||
|
"build:debug": "npm run tauri build -- --debug",
|
||||||
"build:mac": "npm run tauri build -- --target universal-apple-darwin",
|
"build:mac": "npm run tauri build -- --target universal-apple-darwin",
|
||||||
"build:all-unix": "chmod +x ./script/build.sh && ./script/build.sh",
|
"build:all-unix": "chmod +x ./script/build.sh && ./script/build.sh",
|
||||||
"build:all-windows": "pwsh ./script/build.ps1",
|
"build:all-windows": "pwsh ./script/build.ps1",
|
||||||
"analyze": "cd src-tauri && cargo bloat --release --crates",
|
"analyze": "cd src-tauri && cargo bloat --release --crates",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"cli": "rollup -c rollup.config.js --watch",
|
"cli": "rollup -c rollup.config.js --watch",
|
||||||
|
"cli:dev": "cross-env NODE_ENV=development rollup -c rollup.config.js -w",
|
||||||
"cli:build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
|
"cli:build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
|
||||||
"prepublishOnly": "npm run cli:build"
|
"prepublishOnly": "npm run cli:build"
|
||||||
},
|
},
|
||||||
@@ -66,6 +68,7 @@
|
|||||||
"@rollup/plugin-alias": "^4.0.2",
|
"@rollup/plugin-alias": "^4.0.2",
|
||||||
"@rollup/plugin-commonjs": "^23.0.2",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-json": "^5.0.2",
|
"@rollup/plugin-json": "^5.0.2",
|
||||||
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
"@rollup/plugin-terser": "^0.1.0",
|
"@rollup/plugin-terser": "^0.1.0",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/is-url": "^1.2.30",
|
"@types/is-url": "^1.2.30",
|
||||||
|
|||||||
50
plugins/pakeCliDevPlugin.js
Normal file
50
plugins/pakeCliDevPlugin.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import {spawn, exec} from 'child_process';
|
||||||
|
|
||||||
|
|
||||||
|
// just run in development mode
|
||||||
|
export default function pakeCliDevPlugin() {
|
||||||
|
let devChildProcess;
|
||||||
|
let cliChildProcess;
|
||||||
|
|
||||||
|
let devHasStarted = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'pake-cli-dev-plugin',
|
||||||
|
buildEnd() {
|
||||||
|
|
||||||
|
const command = 'node';
|
||||||
|
const cliCmdArgs = ['./dist/dev.js'];
|
||||||
|
|
||||||
|
cliChildProcess = spawn(command, cliCmdArgs, {detached: true});
|
||||||
|
|
||||||
|
cliChildProcess.stdout.on('data', (data) => {
|
||||||
|
console.log(chalk.green(data.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
cliChildProcess.stderr.on('data', (data) => {
|
||||||
|
console.error(chalk.yellow(data.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
cliChildProcess.on('close', async (code) => {
|
||||||
|
console.log(chalk.yellow(`cli running end with code: ${code}`));
|
||||||
|
if (devHasStarted) return;
|
||||||
|
devHasStarted = true;
|
||||||
|
devChildProcess = await exec('npm run tauri dev -- --config ./src-tauri/.pake/tauri.conf.json --features cli-build');
|
||||||
|
|
||||||
|
devChildProcess.stdout.on('data', (data) => {
|
||||||
|
console.log(chalk.green(data.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
devChildProcess.stderr.on('data', (data) => {
|
||||||
|
console.error(chalk.yellow(data.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
devChildProcess.on('close', (code) => {
|
||||||
|
console.log(chalk.yellow(`dev running end: ${code}`));
|
||||||
|
process.exit(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
rollup.config.js
vendored
21
rollup.config.js
vendored
@@ -4,12 +4,23 @@ import typescript from 'rollup-plugin-typescript2';
|
|||||||
import alias from '@rollup/plugin-alias';
|
import alias from '@rollup/plugin-alias';
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
import json from '@rollup/plugin-json';
|
import json from '@rollup/plugin-json';
|
||||||
|
import replace from '@rollup/plugin-replace';
|
||||||
|
|
||||||
|
import pakeCliDevPlugin from './plugins/pakeCliDevPlugin.js';
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
const devPlugins = !isProduction ? [pakeCliDevPlugin()] : [];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
input: 'bin/cli.ts',
|
input: isProduction ? 'bin/cli.ts' : 'bin/dev.ts',
|
||||||
output: {
|
output: {
|
||||||
file: 'dist/cli.js',
|
file: isProduction ? 'dist/cli.js' : 'dist/dev.js',
|
||||||
format: 'es'
|
format: 'es',
|
||||||
|
sourcemap: !isProduction,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
include: 'bin/**',
|
||||||
|
exclude: 'node_modules/**',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
json(),
|
json(),
|
||||||
@@ -18,8 +29,12 @@ export default {
|
|||||||
clean: true, // 清理缓存
|
clean: true, // 清理缓存
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
|
replace({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||||
|
}),
|
||||||
alias({
|
alias({
|
||||||
entries: [{ find: '@', replacement: path.join(appRootPath.path, 'bin') }],
|
entries: [{ find: '@', replacement: path.join(appRootPath.path, 'bin') }],
|
||||||
}),
|
}),
|
||||||
|
...devPlugins,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-works
|
|||||||
cargo-bloat = "0.11.1"
|
cargo-bloat = "0.11.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
# this feature is used for development builds from development cli
|
||||||
|
cli-build = []
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
default = ["custom-protocol"]
|
default = ["custom-protocol"]
|
||||||
|
|||||||
BIN
src-tauri/icons/figma.icns
Normal file
BIN
src-tauri/icons/figma.icns
Normal file
Binary file not shown.
BIN
src-tauri/icons/zbook.icns
Normal file
BIN
src-tauri/icons/zbook.icns
Normal file
Binary file not shown.
@@ -1,28 +1,29 @@
|
|||||||
{
|
{
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"url": "https://weread.qq.com/",
|
"url": "https://weread.qq.com/",
|
||||||
"transparent": true,
|
"transparent": false,
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"width": 1200,
|
"width": 1200,
|
||||||
"height": 780,
|
"height": 780,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"url_type": "web"
|
"url_type": "web"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_agent": {
|
"user_agent": {
|
||||||
"macos": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
|
"macos": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
|
||||||
"linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
"linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||||
"windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
|
"windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"macos": true,
|
"macos": false,
|
||||||
"linux": false,
|
"linux": false,
|
||||||
"windows": false
|
"windows": false
|
||||||
},
|
},
|
||||||
"system_tray": {
|
"system_tray": {
|
||||||
"macos": false,
|
"macos": false,
|
||||||
"linux": true,
|
"linux": true,
|
||||||
"windows": true
|
"windows": true
|
||||||
}
|
},
|
||||||
|
"inject": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::util::{check_file_or_append, get_download_message, show_toast, MessageType};
|
use crate::util::{check_file_or_append, get_download_message, show_toast, MessageType};
|
||||||
use std::fs::File;
|
use std::fs::{self, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tauri::api::http::{ClientBuilder, HttpRequestBuilder, ResponseType};
|
use tauri::api::http::{ClientBuilder, HttpRequestBuilder, ResponseType};
|
||||||
use tauri::{api, command, AppHandle, Manager, Window};
|
use tauri::{api, command, AppHandle, Manager, Window};
|
||||||
@@ -10,6 +10,12 @@ pub struct DownloadFileParams {
|
|||||||
filename: String,
|
filename: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct BinaryDownloadParams {
|
||||||
|
filename: String,
|
||||||
|
binary: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result<(), String> {
|
pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result<(), String> {
|
||||||
let window: Window = app.get_window("pake").unwrap();
|
let window: Window = app.get_window("pake").unwrap();
|
||||||
@@ -42,3 +48,25 @@ pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub async fn download_file_by_binary(
|
||||||
|
app: AppHandle,
|
||||||
|
params: BinaryDownloadParams,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let window: Window = app.get_window("pake").unwrap();
|
||||||
|
show_toast(&window, &get_download_message(MessageType::Start));
|
||||||
|
let output_path = api::path::download_dir().unwrap().join(params.filename);
|
||||||
|
let file_path = check_file_or_append(output_path.to_str().unwrap());
|
||||||
|
let download_file_result = fs::write(file_path, ¶ms.binary);
|
||||||
|
match download_file_result {
|
||||||
|
Ok(_) => {
|
||||||
|
show_toast(&window, &get_download_message(MessageType::Success));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
show_toast(&window, &get_download_message(MessageType::Failure));
|
||||||
|
Err(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ pub fn get_window(app: &mut App, config: PakeConfig, _data_dir: PathBuf) -> Wind
|
|||||||
.resizable(window_config.resizable)
|
.resizable(window_config.resizable)
|
||||||
.fullscreen(window_config.fullscreen)
|
.fullscreen(window_config.fullscreen)
|
||||||
.inner_size(window_config.width, window_config.height)
|
.inner_size(window_config.width, window_config.height)
|
||||||
.disable_file_drop_handler() //Very annoying, otherwise dragging files to the window will not work.
|
.disable_file_drop_handler()
|
||||||
.initialization_script(include_str!("../inject/style.js"))
|
.initialization_script(include_str!("../inject/component.js"))
|
||||||
.initialization_script(include_str!("../inject/event.js"))
|
.initialization_script(include_str!("../inject/event.js"))
|
||||||
.initialization_script(include_str!("../inject/component.js"));
|
.initialization_script(include_str!("../inject/style.js"))
|
||||||
|
.initialization_script(include_str!("../inject/custom.js")); //Very annoying, otherwise dragging files to the window will not work.
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
|
|||||||
2
src-tauri/src/inject/custom.js
vendored
Normal file
2
src-tauri/src/inject/custom.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// This file is used to merge the injected external js and css files
|
||||||
|
// and you can also directly add script files that you want to attach to the application in this fil
|
||||||
156
src-tauri/src/inject/event.js
vendored
156
src-tauri/src/inject/event.js
vendored
@@ -68,12 +68,90 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.body.appendChild(topDom);
|
document.body.appendChild(topDom);
|
||||||
const domEl = document.getElementById('pack-top-dom');
|
const domEl = document.getElementById('pack-top-dom');
|
||||||
|
|
||||||
domEl.addEventListener('mousedown', (e) => {
|
|
||||||
e.preventDefault();
|
// Collect blob urls to blob by overriding window.URL.createObjectURL
|
||||||
if (e.buttons === 1 && e.detail !== 2) {
|
function collectUrlToBlobs() {
|
||||||
appWindow.startDragging().then();
|
const backupCreateObjectURL = window.URL.createObjectURL;
|
||||||
}
|
window.blobToUrlCaches = new Map();
|
||||||
|
window.URL.createObjectURL = (blob) => {
|
||||||
|
const url = backupCreateObjectURL.call(window.URL, blob);
|
||||||
|
window.blobToUrlCaches.set(url, blob);
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBlobUrlToBinary(blobUrl) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const blob = window.blobToUrlCaches.get(blobUrl);
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve(Array.from(new Uint8Array(reader.result)));
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function downladFromDataUri(dataURI, filename) {
|
||||||
|
const byteString = atob(dataURI.split(',')[1]);
|
||||||
|
// write the bytes of the string to an ArrayBuffer
|
||||||
|
const bufferArray = new ArrayBuffer(byteString.length);
|
||||||
|
|
||||||
|
// create a view into the buffer
|
||||||
|
const binary = new Uint8Array(bufferArray);
|
||||||
|
|
||||||
|
// set the bytes of the buffer to the correct values
|
||||||
|
for (var i = 0; i < byteString.length; i++) {
|
||||||
|
binary[i] = byteString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the ArrayBuffer to a binary, and you're done
|
||||||
|
invoke('download_file_by_binary', {
|
||||||
|
params: {
|
||||||
|
filename,
|
||||||
|
binary: Array.from(binary)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFromBlobUrl(blobUrl, filename) {
|
||||||
|
convertBlobUrlToBinary(blobUrl).then((binary) => {
|
||||||
|
invoke('download_file_by_binary', {
|
||||||
|
params: {
|
||||||
|
filename,
|
||||||
|
binary
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// detect blob download by createElement("a")
|
||||||
|
function detectDownloadByCreateAnchor() {
|
||||||
|
const createEle = document.createElement;
|
||||||
|
document.createElement = (el) => {
|
||||||
|
if (el !== 'a') return createEle.call(document, el);
|
||||||
|
const anchorEle = createEle.call(document, el);
|
||||||
|
|
||||||
|
// use addEventListener to avoid overriding the original click event.
|
||||||
|
anchorEle.addEventListener('click', (e) => {
|
||||||
|
const url = anchorEle.href;
|
||||||
|
const filename = anchorEle.download || getFilenameFromUrl(url);
|
||||||
|
if (window.blobToUrlCaches.has(url)) {
|
||||||
|
downloadFromBlobUrl(url, filename);
|
||||||
|
// case: downoload from dataURL -> convert dataURL ->
|
||||||
|
} else if (url.startsWith('data:')) {
|
||||||
|
downladFromDataUri(url, filename);
|
||||||
|
} else {
|
||||||
|
handleExternalLink(e, url);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
return anchorEle;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
domEl.addEventListener('touchstart', () => {
|
domEl.addEventListener('touchstart', () => {
|
||||||
appWindow.startDragging().then();
|
appWindow.startDragging().then();
|
||||||
@@ -95,6 +173,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isExternalLink = (url, host) => window.location.host !== host;
|
const isExternalLink = (url, host) => window.location.host !== host;
|
||||||
|
// process special download protocal['data:','blob:']
|
||||||
|
const isSpecialDownload = (url) => ['blob', 'data'].some(protocal => url.startsWith(protocal));
|
||||||
|
|
||||||
const isDownloadRequired = (url, anchorElement, e) =>
|
const isDownloadRequired = (url, anchorElement, e) =>
|
||||||
anchorElement.download || e.metaKey || e.ctrlKey || isDownloadLink(url);
|
anchorElement.download || e.metaKey || e.ctrlKey || isDownloadLink(url);
|
||||||
|
|
||||||
@@ -122,7 +203,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process download links for Rust to handle.
|
// Process download links for Rust to handle.
|
||||||
if (isDownloadRequired(absoluteUrl, anchorElement, e) && !externalDownLoadLink()) {
|
if (isDownloadRequired(absoluteUrl, anchorElement, e) && !externalDownLoadLink() && !isSpecialDownload(absoluteUrl)) {
|
||||||
handleDownloadLink(e, absoluteUrl, filename);
|
handleDownloadLink(e, absoluteUrl, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,69 +258,6 @@ function getFilenameFromUrl(url) {
|
|||||||
return urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
return urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect blob urls to blob by overriding window.URL.createObjectURL
|
|
||||||
function collectUrlToBlobs() {
|
|
||||||
const backupCreateObjectURL = window.URL.createObjectURL;
|
|
||||||
window.blobToUrlCaches = new Map();
|
|
||||||
window.URL.createObjectURL = (blob) => {
|
|
||||||
const url = backupCreateObjectURL.call(window.URL, blob);
|
|
||||||
window.blobToUrlCaches.set(url, blob);
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertBlobUrlToBinary(blobUrl) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const blob = window.blobToUrlCaches.get(blobUrl);
|
|
||||||
const reader = new FileReader();
|
|
||||||
|
|
||||||
reader.readAsArrayBuffer(blob);
|
|
||||||
reader.onload = () => {
|
|
||||||
resolve(reader.result);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadFromBlobUrl(blobUrl, filename) {
|
|
||||||
try {
|
|
||||||
const tauri = window.__TAURI__;
|
|
||||||
const binary = await convertBlobUrlToBinary(blobUrl);
|
|
||||||
|
|
||||||
await tauri.fs.writeBinaryFile(filename, binary, {
|
|
||||||
dir: tauri.fs.BaseDirectory.Download,
|
|
||||||
});
|
|
||||||
|
|
||||||
const lang = getSystemLanguage();
|
|
||||||
window.pakeToast(lang === 'en' ? 'Download successful, saved to download directory~' : '下载成功,已保存到下载目录~');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error downloading from Blob URL:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// detect blob download by createElement("a")
|
|
||||||
function detectDownloadByCreateAnchor() {
|
|
||||||
const originalCreateElement = document.createElement;
|
|
||||||
|
|
||||||
document.createElement = function(el, ...args) {
|
|
||||||
const element = originalCreateElement.call(this, el, ...args);
|
|
||||||
|
|
||||||
if (el === 'a') {
|
|
||||||
element.addEventListener('click', (event) => {
|
|
||||||
const url = element.href;
|
|
||||||
if (window.blobToUrlCaches.has(url)) {
|
|
||||||
// Prevent default 'click' event if a blob URL is detected
|
|
||||||
event.preventDefault();
|
|
||||||
const filename = element.download || getFilenameFromUrl(url);
|
|
||||||
downloadFromBlobUrl(url, filename);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Determine the language of the current system.
|
// Determine the language of the current system.
|
||||||
function getSystemLanguage() {
|
function getSystemLanguage() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ mod app;
|
|||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use app::{invoke, menu, window};
|
use app::{invoke, menu, window};
|
||||||
use invoke::download_file;
|
use invoke::{download_file, download_file_by_binary};
|
||||||
use menu::{get_menu, menu_event_handle};
|
use menu::{get_menu, menu_event_handle};
|
||||||
use tauri_plugin_window_state::Builder as windowStatePlugin;
|
use tauri_plugin_window_state::Builder as windowStatePlugin;
|
||||||
use util::{get_data_dir, get_pake_config};
|
use util::{get_data_dir, get_pake_config};
|
||||||
@@ -41,7 +41,10 @@ pub fn run_app() {
|
|||||||
|
|
||||||
tauri_app
|
tauri_app
|
||||||
.plugin(windowStatePlugin::default().build())
|
.plugin(windowStatePlugin::default().build())
|
||||||
.invoke_handler(tauri::generate_handler![download_file])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
download_file,
|
||||||
|
download_file_by_binary
|
||||||
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let _window = get_window(app, pake_config, data_dir);
|
let _window = get_window(app, pake_config, data_dir);
|
||||||
// Prevent initial shaking
|
// Prevent initial shaking
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ use std::path::PathBuf;
|
|||||||
use tauri::{api, Config, Window};
|
use tauri::{api, Config, Window};
|
||||||
|
|
||||||
pub fn get_pake_config() -> (PakeConfig, Config) {
|
pub fn get_pake_config() -> (PakeConfig, Config) {
|
||||||
|
#[cfg(feature = "cli-build")]
|
||||||
|
let pake_config: PakeConfig = serde_json::from_str(include_str!("../.pake/pake.json"))
|
||||||
|
.expect("Failed to parse pake config");
|
||||||
|
#[cfg(not(feature = "cli-build"))]
|
||||||
let pake_config: PakeConfig =
|
let pake_config: PakeConfig =
|
||||||
serde_json::from_str(include_str!("../pake.json")).expect("Failed to parse pake config");
|
serde_json::from_str(include_str!("../pake.json")).expect("Failed to parse pake config");
|
||||||
|
#[cfg(feature = "cli-build")]
|
||||||
|
let tauri_config: Config = serde_json::from_str(include_str!("../.pake/tauri.conf.json"))
|
||||||
|
.expect("Failed to parse tauri config");
|
||||||
|
#[cfg(not(feature = "cli-build"))]
|
||||||
let tauri_config: Config = serde_json::from_str(include_str!("../tauri.conf.json"))
|
let tauri_config: Config = serde_json::from_str(include_str!("../tauri.conf.json"))
|
||||||
.expect("Failed to parse tauri config");
|
.expect("Failed to parse tauri config");
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,43 @@
|
|||||||
{
|
{
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "WeRead",
|
"productName": "WeRead",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null,
|
"csp": null,
|
||||||
"dangerousRemoteDomainIpcAccess": [
|
"dangerousRemoteDomainIpcAccess": [
|
||||||
{
|
{
|
||||||
"domain": "weread.qq.com",
|
"domain": "weread.qq.com",
|
||||||
"windows": ["pake"],
|
"windows": [
|
||||||
"enableTauriAPI": true
|
"pake"
|
||||||
|
],
|
||||||
|
"enableTauriAPI": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
"systemTray": {
|
||||||
|
"iconPath": "png/icon_512.png",
|
||||||
|
"iconAsTemplate": true
|
||||||
|
},
|
||||||
|
"allowlist": {
|
||||||
|
"all": true,
|
||||||
|
"fs": {
|
||||||
|
"all": true,
|
||||||
|
"scope": [
|
||||||
|
"$DOWNLOAD/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"updater": {
|
"build": {
|
||||||
"active": false
|
"withGlobalTauri": true,
|
||||||
},
|
"devPath": "../dist",
|
||||||
"systemTray": {
|
"distDir": "../dist",
|
||||||
"iconPath": "png/weread_512.png",
|
"beforeBuildCommand": "",
|
||||||
"iconAsTemplate": true
|
"beforeDevCommand": ""
|
||||||
},
|
|
||||||
"allowlist": {
|
|
||||||
"all": true,
|
|
||||||
"fs": {
|
|
||||||
"all": true,
|
|
||||||
"scope": ["$DOWNLOAD/*"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"withGlobalTauri": true,
|
|
||||||
"devPath": "../dist",
|
|
||||||
"distDir": "../dist",
|
|
||||||
"beforeBuildCommand": "",
|
|
||||||
"beforeDevCommand": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
{
|
{
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"icon": ["icons/weread.icns"],
|
"icon": ["icons/weread.icns"],
|
||||||
"identifier": "com.pake.weread",
|
"identifier": "com.pake.weread",
|
||||||
"active": true,
|
"active": true,
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"copyright": "",
|
"copyright": "",
|
||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
"longDescription": "",
|
"longDescription": "",
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"entitlements": null,
|
"entitlements": null,
|
||||||
"exceptionDomain": "",
|
"exceptionDomain": "",
|
||||||
"frameworks": [],
|
"frameworks": [],
|
||||||
"providerShortName": null,
|
"providerShortName": null,
|
||||||
"signingIdentity": null
|
"signingIdentity": null
|
||||||
},
|
},
|
||||||
"resources": [],
|
"resources": [],
|
||||||
"shortDescription": "",
|
"shortDescription": "",
|
||||||
"targets": ["dmg"]
|
"targets": [
|
||||||
|
"dmg"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user