Compare commits

...

2 Commits

Author SHA1 Message Date
af61d23fd4 feat: get pwa working 2023-05-22 00:29:27 +01:00
c6c373f9fc refactor: make console log less verbose when not needed 2023-05-22 00:00:35 +01:00
13 changed files with 2087 additions and 35 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
# Example environment file for vite to use.
# For more information, see: https://vitejs.dev/guide/env-and-mode.html
ENABLE_PERF_LOG=0

View File

@ -4,6 +4,11 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>音乐解锁 - Unlock Music</title>
<meta name="description" content="音乐解锁 - Unlock Music" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/pwa-512x512.png" sizes="512x512" />
<meta name="theme-color" content="#4DBA87" />
</head>
<body>
<main id="root"></main>

View File

@ -54,6 +54,7 @@
"prettier": "^2.8.8",
"typescript": "^5.0.2",
"vite": "^4.3.2",
"vite-plugin-pwa": "^0.15.0",
"vite-plugin-top-level-await": "^1.3.0",
"vite-plugin-wasm": "^3.2.2",
"vitest": "^0.31.0"

File diff suppressed because it is too large Load Diff

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

BIN
public/pwa-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/pwa-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1 @@
export class UnsupportedSourceFile extends Error {}

View File

@ -1,5 +1,6 @@
import { Transformer, Parakeet, TransformResult, fetchParakeet } from '@jixun/libparakeet';
import { toArrayBuffer } from './buffer';
import { UnsupportedSourceFile } from './DecryptError';
export async function transformBlob(
blob: Blob | ArrayBuffer,
@ -21,7 +22,9 @@ export async function transformBlob(
cleanup.push(() => writer.delete());
const result = transformer.Transform(writer, reader);
if (result !== TransformResult.OK) {
if (result === TransformResult.ERROR_INVALID_FORMAT) {
throw new UnsupportedSourceFile(`transformer<${transformer.Name}> does not recognize this file`);
} else if (result !== TransformResult.OK) {
throw new Error(`transformer<${transformer.Name}> failed with error: ${TransformResult[result]} (${result})`);
}

View File

@ -1,8 +1,9 @@
import { Parakeet, fetchParakeet } from '@jixun/libparakeet';
import { timedLogger } from '~/util/timedLogger';
import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils';
import { allCryptoFactories } from '../../crypto/CryptoFactory';
import { toArrayBuffer, toBlob } from '~/decrypt-worker/util/buffer';
import { CryptoBase, CryptoFactory } from '~/decrypt-worker/crypto/CryptoBase';
import { UnsupportedSourceFile } from '~/decrypt-worker/util/DecryptError';
// Use first 4MiB of the file to perform check.
const TEST_FILE_HEADER_LEN = 4 * 1024 * 1024;
@ -29,8 +30,11 @@ class DecryptCommandHandler {
}
return result;
} catch (error) {
console.error('decrypt failed: ', error);
continue;
if (error instanceof UnsupportedSourceFile) {
console.debug('WARN: decryptor does not recognize source file, wrong crypto?', error);
} else {
console.error('decrypt failed with unknown error: ', error);
}
}
}
@ -83,18 +87,12 @@ class DecryptCommandHandler {
export const workerDecryptHandler = async ({ id, blobURI }: { id: string; blobURI: string }) => {
const label = `decrypt( ${id} )`;
console.group(label);
return withTimeGroupedLogs(label, async () => {
const parakeet = await timedLogger(`${label}/init`, fetchParakeet);
const blob = await timedLogger(`${label}/fetch-src`, async () => fetch(blobURI).then((r) => r.blob()));
const buffer = await timedLogger(`${label}/read-src`, async () => blob.arrayBuffer());
try {
return await timedLogger(`${label}/total`, async () => {
const parakeet = await timedLogger(`${label}/init`, fetchParakeet);
const blob = await timedLogger(`${label}/fetch-src`, async () => fetch(blobURI).then((r) => r.blob()));
const buffer = await timedLogger(`${label}/read-src`, async () => blob.arrayBuffer());
const handler = new DecryptCommandHandler(id, parakeet, blob, buffer);
return handler.decrypt(allCryptoFactories);
});
} finally {
(console.groupEnd as (label: string) => void)(label);
}
const handler = new DecryptCommandHandler(id, parakeet, blob, buffer);
return handler.decrypt(allCryptoFactories);
});
};

View File

@ -2,21 +2,19 @@ function isPromise<T = unknown>(p: unknown): p is Promise<T> {
return !!p && typeof p === 'object' && 'then' in p && 'catch' in p && 'finally' in p;
}
export function timedLogger<R = unknown>(label: string, fn: () => R): R {
console.time(label);
export function wrapFunctionCall<R = unknown>(pre: () => void, post: () => void, fn: () => R): R {
pre();
try {
const result = fn();
if (isPromise(result)) {
result.finally(() => {
console.timeEnd(label);
});
result.finally(post);
}
return result;
} catch (e) {
console.timeEnd(label);
post();
throw e;
}
}

25
src/util/logUtils.ts Normal file
View File

@ -0,0 +1,25 @@
import { wrapFunctionCall } from './fnWrapper';
export function timedLogger<R = unknown>(label: string, fn: () => R): R {
if (import.meta.env.ENABLE_PERF_LOG !== '1') {
return fn();
} else {
return wrapFunctionCall(
() => console.time(label),
() => console.timeEnd(label),
fn
);
}
}
export function withGroupedLogs<R = unknown>(label: string, fn: () => R): R {
if (import.meta.env.ENABLE_PERF_LOG !== '1') {
return fn();
} else {
return wrapFunctionCall(
() => console.group(label),
() => (console.groupEnd as (label: string) => void)(label),
() => timedLogger(`${label}/total`, fn)
);
}
}

View File

@ -8,6 +8,7 @@ import react from '@vitejs/plugin-react';
import wasm from 'vite-plugin-wasm';
import replace from '@rollup/plugin-replace';
import topLevelAwait from 'vite-plugin-top-level-await';
import { VitePWA } from 'vite-plugin-pwa';
const gitRoot = url.fileURLToPath(new URL('.', import.meta.url));
const pkg = JSON.parse(fs.readFileSync(gitRoot + '/package.json', 'utf-8'));
@ -52,6 +53,34 @@ export default defineConfig({
react(),
wasm(),
topLevelAwait(),
VitePWA({
registerType: 'prompt',
workbox: {
// Cache everything from dist
globPatterns: ['**/*.{js,css,html,ico,png,svg,wasm}'],
},
manifest: {
display: 'standalone',
name: '音乐解锁 (Unlock Music)',
short_name: '音乐解锁',
lang: 'zh-cmn-Hans-CN',
description: '在现代浏览器解锁已购的加密音乐!',
theme_color: '#ffffff',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
}),
],
resolve: {
alias: {