Compare commits
2 commits
e99ec8e88a
...
b59f40830b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b59f40830b | ||
![]() |
e43dd2c336 |
20 changed files with 1450 additions and 211 deletions
|
@ -5,7 +5,6 @@ edition = "2021"
|
||||||
description = "Get the raw database from your Signal backup. Written for webassembly"
|
description = "Get the raw database from your Signal backup. Written for webassembly"
|
||||||
repository = "https://git.duskflower.dev/duskflower/signal-decrypt-backup-wasm"
|
repository = "https://git.duskflower.dev/duskflower/signal-decrypt-backup-wasm"
|
||||||
license = "GPL-3-only"
|
license = "GPL-3-only"
|
||||||
license-file = "LICENSE"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -41,14 +41,14 @@ type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct DecryptionResult {
|
pub struct DecryptionResult {
|
||||||
database_bytes: Vec<u8>,
|
database_statements: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl DecryptionResult {
|
impl DecryptionResult {
|
||||||
#[wasm_bindgen(getter)]
|
#[wasm_bindgen(getter)]
|
||||||
pub fn database_bytes(&self) -> Vec<u8> {
|
pub fn database_statements(&self) -> Vec<String> {
|
||||||
self.database_bytes.clone()
|
self.database_statements.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,7 +355,7 @@ pub struct BackupDecryptor {
|
||||||
keys: Option<Keys>,
|
keys: Option<Keys>,
|
||||||
header_data: Option<HeaderData>,
|
header_data: Option<HeaderData>,
|
||||||
initialisation_vector: Option<Vec<u8>>,
|
initialisation_vector: Option<Vec<u8>>,
|
||||||
database_bytes: Vec<u8>,
|
database_statements: Vec<String>,
|
||||||
ciphertext_buf: Vec<u8>,
|
ciphertext_buf: Vec<u8>,
|
||||||
plaintext_buf: Vec<u8>,
|
plaintext_buf: Vec<u8>,
|
||||||
total_file_size: usize,
|
total_file_size: usize,
|
||||||
|
@ -379,7 +379,7 @@ impl BackupDecryptor {
|
||||||
keys: None,
|
keys: None,
|
||||||
header_data: None,
|
header_data: None,
|
||||||
initialisation_vector: None,
|
initialisation_vector: None,
|
||||||
database_bytes: Vec::new(),
|
database_statements: Vec::new(),
|
||||||
ciphertext_buf: Vec::new(),
|
ciphertext_buf: Vec::new(),
|
||||||
plaintext_buf: Vec::new(),
|
plaintext_buf: Vec::new(),
|
||||||
total_file_size: 0,
|
total_file_size: 0,
|
||||||
|
@ -544,8 +544,7 @@ impl BackupDecryptor {
|
||||||
if let Some(version) = backup_frame.version {
|
if let Some(version) = backup_frame.version {
|
||||||
if let Some(ver_num) = version.version {
|
if let Some(ver_num) = version.version {
|
||||||
let pragma_sql = format!("PRAGMA user_version = {}", ver_num);
|
let pragma_sql = format!("PRAGMA user_version = {}", ver_num);
|
||||||
self.database_bytes.extend_from_slice(pragma_sql.as_bytes());
|
self.database_statements.push(pragma_sql);
|
||||||
self.database_bytes.push(b';');
|
|
||||||
}
|
}
|
||||||
} else if let Some(statement) = backup_frame.statement {
|
} else if let Some(statement) = backup_frame.statement {
|
||||||
if let Some(sql) = statement.statement {
|
if let Some(sql) = statement.statement {
|
||||||
|
@ -562,13 +561,10 @@ impl BackupDecryptor {
|
||||||
|
|
||||||
process_parameter_placeholders(&sql, ¶ms)?
|
process_parameter_placeholders(&sql, ¶ms)?
|
||||||
} else {
|
} else {
|
||||||
sql
|
sql.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add to concatenated string
|
self.database_statements.push(processed_sql);
|
||||||
self.database_bytes
|
|
||||||
.extend_from_slice(processed_sql.as_bytes());
|
|
||||||
self.database_bytes.push(b';');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if backup_frame.preference.is_some() || backup_frame.key_value.is_some()
|
} else if backup_frame.preference.is_some() || backup_frame.key_value.is_some()
|
||||||
|
@ -628,7 +624,7 @@ impl BackupDecryptor {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn finish(self) -> Result<DecryptionResult, JsValue> {
|
pub fn finish(self) -> Result<DecryptionResult, JsValue> {
|
||||||
Ok(DecryptionResult {
|
Ok(DecryptionResult {
|
||||||
database_bytes: self.database_bytes,
|
database_statements: self.database_statements,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
test/.gitignore
vendored
Normal file
29
test/.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
dist
|
||||||
|
.solid
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.vinxi
|
||||||
|
app.config.timestamp_*.js
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
gitignore
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
|
@ -1,15 +1,36 @@
|
||||||
# test
|
## Usage
|
||||||
|
|
||||||
To install dependencies:
|
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
|
||||||
|
|
||||||
|
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
$ npm install # or pnpm install or yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
To run:
|
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||||
|
|
||||||
```bash
|
## Available Scripts
|
||||||
bun run index.js
|
|
||||||
```
|
|
||||||
|
|
||||||
This project was created using `bun init` in bun v1.1.20. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm run dev` or `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.<br>
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.<br>
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `dist` folder.<br>
|
||||||
|
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.<br>
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
|
||||||
|
|
||||||
|
## This project was created with the [Solid CLI](https://solid-cli.netlify.app)
|
||||||
|
|
21
test/dist/index.html
vendored
21
test/dist/index.html
vendored
|
@ -1,21 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<!-- <link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" /> -->
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root">
|
|
||||||
<form id="test-form">
|
|
||||||
<input type="password" id="passphrase-input" />
|
|
||||||
<input type="file" id="backup-input" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="./index.js" type="module"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
100
test/dist/index.js
vendored
100
test/dist/index.js
vendored
|
@ -1,100 +0,0 @@
|
||||||
import init, { BackupDecryptor } from "../pkg/signal_decrypt_backup_wasm.js";
|
|
||||||
|
|
||||||
let isInitialized = false;
|
|
||||||
|
|
||||||
async function initialize() {
|
|
||||||
if (!isInitialized) {
|
|
||||||
await init().catch((err) =>
|
|
||||||
console.error("Failed to initialize WASM module: ", err),
|
|
||||||
);
|
|
||||||
isInitialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function decryptBackup(file, passphrase, progressCallback) {
|
|
||||||
await initialize();
|
|
||||||
|
|
||||||
const fileSize = file.size;
|
|
||||||
console.log("Starting decryption of file size:", fileSize);
|
|
||||||
const decryptor = new BackupDecryptor();
|
|
||||||
decryptor.set_progress_callback(fileSize, (percentage) =>
|
|
||||||
console.info(`${percentage}% done`),
|
|
||||||
);
|
|
||||||
const chunkSize = 1024 * 1024 * 40; // 40MB chunks
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (offset < file.size) {
|
|
||||||
// console.log(`Processing chunk at offset ${offset}`);
|
|
||||||
const chunk = file.slice(offset, offset + chunkSize);
|
|
||||||
const arrayBuffer = await chunk.arrayBuffer();
|
|
||||||
const uint8Array = new Uint8Array(arrayBuffer);
|
|
||||||
|
|
||||||
decryptor.feed_data(uint8Array);
|
|
||||||
|
|
||||||
let done = false;
|
|
||||||
while (!done) {
|
|
||||||
try {
|
|
||||||
done = await decryptor.process_chunk(passphrase, progressCallback);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error processing chunk:", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += chunkSize;
|
|
||||||
// console.log(`Completed chunk, new offset: ${offset}`);
|
|
||||||
// if (performance.memory) {
|
|
||||||
// const memoryInfo = performance.memory;
|
|
||||||
// console.log(`Total JS Heap Size: ${memoryInfo.totalJSHeapSize} bytes`);
|
|
||||||
// console.log(`Used JS Heap Size: ${memoryInfo.usedJSHeapSize} bytes`);
|
|
||||||
// console.log(`JS Heap Size Limit: ${memoryInfo.jsHeapSizeLimit} bytes`);
|
|
||||||
// } else {
|
|
||||||
// console.log("Memory information is not available in this environment.");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log("All chunks processed, finishing up");
|
|
||||||
const result = decryptor.finish();
|
|
||||||
|
|
||||||
decryptor.free();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Decryption failed:", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function decrypt(file, passphrase) {
|
|
||||||
try {
|
|
||||||
const result = await decryptBackup(file, passphrase);
|
|
||||||
|
|
||||||
console.log(result, result.database_bytes);
|
|
||||||
|
|
||||||
// console.log("Database bytes length:", result.databaseBytes.length);
|
|
||||||
console.log(
|
|
||||||
"Database bytes as string (partly)",
|
|
||||||
new TextDecoder().decode(result.database_bytes.slice(0, 1024 * 50)),
|
|
||||||
);
|
|
||||||
// console.log("Preferences:", result.preferences);
|
|
||||||
// console.log("Key values:", result.keyValues);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Decryption failed:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {HTMLInputElement}
|
|
||||||
*/
|
|
||||||
const passphraseInput = document.querySelector("#passphrase-input");
|
|
||||||
/**
|
|
||||||
* @type {HTMLInputElement}
|
|
||||||
*/
|
|
||||||
const backupInput = document.querySelector("#backup-input");
|
|
||||||
|
|
||||||
backupInput.addEventListener("change", (event) => {
|
|
||||||
const file = event.currentTarget.files[0];
|
|
||||||
|
|
||||||
decrypt(file, passphraseInput.value);
|
|
||||||
});
|
|
16
test/index.html
Normal file
16
test/index.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
|
||||||
|
<title>Solid App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script src="/src/index.tsx" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,34 +0,0 @@
|
||||||
const server = Bun.serve({
|
|
||||||
static: {
|
|
||||||
"/": new Response(await Bun.file("./dist/index.html").bytes(), {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/html",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
"/index.js": new Response(await Bun.file("./dist/index.js").bytes(), {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/javascript",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
"/pkg/signal_decrypt_backup_wasm.js": new Response(
|
|
||||||
await Bun.file("../pkg/signal_decrypt_backup_wasm.js").bytes(),
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/javascript",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
"/pkg/signal_decrypt_backup_wasm_bg.wasm": new Response(
|
|
||||||
await Bun.file("../pkg/signal_decrypt_backup_wasm_bg.wasm").bytes(),
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/wasm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
fetch(req) {
|
|
||||||
console.log(req);
|
|
||||||
return new Response("404!");
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
// Enable latest features
|
|
||||||
"lib": ["ESNext", "DOM"],
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
|
|
||||||
// Bundler mode
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
// Best practices
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
|
|
||||||
// Some stricter flags (disabled by default)
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noPropertyAccessFromIndexSignature": false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,24 @@
|
||||||
{
|
{
|
||||||
"name": "test",
|
"name": "vite-template-solid",
|
||||||
"module": "index.js",
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"scripts": {
|
||||||
"@types/bun": "latest"
|
"start": "vite",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"license": "MIT",
|
||||||
"typescript": "^5.0.0"
|
"devDependencies": {
|
||||||
|
"@duskflower/signal-decrypt-backup-wasm": "../pkg",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.0",
|
||||||
|
"vite-plugin-solid": "^2.11.0",
|
||||||
|
"vite-plugin-wasm": "^3.4.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@sqlite.org/sqlite-wasm": "3.47.2-build1",
|
||||||
|
"solid-js": "^1.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1089
test/pnpm-lock.yaml
generated
Normal file
1089
test/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
33
test/src/App.module.css
Normal file
33
test/src/App.module.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
animation: logo-spin infinite 20s linear;
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #b318f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
144
test/src/App.tsx
Normal file
144
test/src/App.tsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import { createEffect, createSignal, on, type Component } from "solid-js";
|
||||||
|
|
||||||
|
import { BackupDecryptor } from "@duskflower/signal-decrypt-backup-wasm";
|
||||||
|
import { db } from "./db";
|
||||||
|
|
||||||
|
export async function decryptBackup(
|
||||||
|
file: File,
|
||||||
|
passphrase: string,
|
||||||
|
progressCallback: (progres: number) => void,
|
||||||
|
) {
|
||||||
|
const fileSize = file.size;
|
||||||
|
console.log("Starting decryption of file size:", fileSize);
|
||||||
|
const decryptor = new BackupDecryptor();
|
||||||
|
decryptor.set_progress_callback(fileSize, (percentage: number) =>
|
||||||
|
console.info(`${percentage}% done`),
|
||||||
|
);
|
||||||
|
const chunkSize = 1024 * 1024 * 40; // 40MB chunks
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (offset < file.size) {
|
||||||
|
// console.log(`Processing chunk at offset ${offset}`);
|
||||||
|
const chunk = file.slice(offset, offset + chunkSize);
|
||||||
|
const arrayBuffer = await chunk.arrayBuffer();
|
||||||
|
const uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
|
decryptor.feed_data(uint8Array);
|
||||||
|
|
||||||
|
let done = false;
|
||||||
|
while (!done) {
|
||||||
|
try {
|
||||||
|
done = decryptor.process_chunk(passphrase);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error processing chunk:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += chunkSize;
|
||||||
|
// console.log(`Completed chunk, new offset: ${offset}`);
|
||||||
|
// if (performance.memory) {
|
||||||
|
// const memoryInfo = performance.memory;
|
||||||
|
// console.log(`Total JS Heap Size: ${memoryInfo.totalJSHeapSize} bytes`);
|
||||||
|
// console.log(`Used JS Heap Size: ${memoryInfo.usedJSHeapSize} bytes`);
|
||||||
|
// console.log(`JS Heap Size Limit: ${memoryInfo.jsHeapSizeLimit} bytes`);
|
||||||
|
// } else {
|
||||||
|
// console.log("Memory information is not available in this environment.");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("All chunks processed, finishing up");
|
||||||
|
const result = decryptor.finish();
|
||||||
|
|
||||||
|
// decryptor.free();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Decryption failed:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function decrypt(file: File, passphrase: string) {
|
||||||
|
try {
|
||||||
|
const result = await decryptBackup(file, passphrase, console.info);
|
||||||
|
|
||||||
|
// console.log(result, result.database_bytes);
|
||||||
|
|
||||||
|
// console.log("Database bytes length:", result.databaseBytes.length);
|
||||||
|
// console.log(
|
||||||
|
// "Database bytes as string (partly)",
|
||||||
|
// new TextDecoder().decode(result.database_bytes.slice(0, 1024 * 50)),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(result.database_statements);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
// console.log("Preferences:", result.preferences);
|
||||||
|
// console.log("Key values:", result.keyValues);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Decryption failed:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const App: Component = () => {
|
||||||
|
const [passphrase, setPassphrase] = createSignal("");
|
||||||
|
const [backupFile, setBackupFile] = createSignal<File>();
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
backupFile,
|
||||||
|
(currentBackupFile) => {
|
||||||
|
if (currentBackupFile) {
|
||||||
|
decrypt(currentBackupFile, passphrase()).then((result) => {
|
||||||
|
if (result) {
|
||||||
|
for (const statement of result.database_statements) {
|
||||||
|
try {
|
||||||
|
console.log("executing");
|
||||||
|
db.exec(statement);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`statement failed: ${statement}`, {
|
||||||
|
cause: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All statements executed");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
db.exec("SELECT * from message", {
|
||||||
|
returnValue: "resultRows",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defer: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form id="test-form">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="passphrase-input"
|
||||||
|
onChange={(event) => {
|
||||||
|
setPassphrase(event.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="backup-input"
|
||||||
|
onChange={(event) => {
|
||||||
|
setBackupFile(event.currentTarget.files?.[0]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
BIN
test/src/assets/favicon.ico
Normal file
BIN
test/src/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
21
test/src/db.ts
Normal file
21
test/src/db.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import sqlite3InitModule, {
|
||||||
|
type Database,
|
||||||
|
type OpfsDatabase,
|
||||||
|
} from "@sqlite.org/sqlite-wasm";
|
||||||
|
|
||||||
|
export let db: OpfsDatabase | Database;
|
||||||
|
|
||||||
|
const sqlite3 = (await sqlite3InitModule()).oo1;
|
||||||
|
if (!sqlite3) {
|
||||||
|
throw new Error("fail to load sqlite");
|
||||||
|
}
|
||||||
|
const path = "/signal.db";
|
||||||
|
|
||||||
|
if (sqlite3.OpfsDb) {
|
||||||
|
console.log("support OPFS");
|
||||||
|
db = new sqlite3.OpfsDb(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("doesn't support OPFS");
|
||||||
|
|
||||||
|
db = new sqlite3.DB(path);
|
13
test/src/index.css
Normal file
13
test/src/index.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
|
||||||
|
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||||
|
'Helvetica Neue', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
15
test/src/index.tsx
Normal file
15
test/src/index.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/* @refresh reload */
|
||||||
|
import { render } from 'solid-js/web';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
|
||||||
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
|
throw new Error(
|
||||||
|
'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => <App />, root!);
|
1
test/src/logo.svg
Normal file
1
test/src/logo.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
15
test/tsconfig.json
Normal file
15
test/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"noEmit": true,
|
||||||
|
"isolatedModules": true
|
||||||
|
}
|
||||||
|
}
|
16
test/vite.config.ts
Normal file
16
test/vite.config.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import solidPlugin from "vite-plugin-solid";
|
||||||
|
import wasm from "vite-plugin-wasm";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solidPlugin(), wasm()],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ["signal-decrypt-backup-wasm", "@sqlite.org/sqlite-wasm"],
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: "esnext",
|
||||||
|
},
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue