added pwa

This commit is contained in:
PhilReact 2025-03-05 03:18:51 +02:00
parent e3d7d71c52
commit 299a42ca99
14 changed files with 2215 additions and 261 deletions

2249
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -88,6 +88,7 @@
"tippy.js": "^6.3.7",
"tiptap-extension-resize-image": "^1.1.8",
"ts-key-enum": "^2.0.12",
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-top-level-await": "^1.4.4",
"vite-plugin-wasm": "^3.3.0"
},

BIN
public/qortal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
public/qortal192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -132,7 +132,7 @@ import {
} from "./atoms/global";
import { useAppFullScreen } from "./useAppFullscreen";
import { NotAuthenticated, manifestData } from "./ExtStates/NotAuthenticated";
import { openIndexedDB, showSaveFilePicker } from "./components/Apps/useQortalMessageListener";
import { isNative, openIndexedDB, showSaveFilePicker } from "./components/Apps/useQortalMessageListener";
import { fileToBase64 } from "./utils/fileReading";
import { handleGetFileFromIndexedDB } from "./utils/indexedDB";
import { Wallets } from "./Wallets";
@ -499,7 +499,7 @@ function App() {
const seedPhrase = generatorRef.current.parsedString
saveSeedPhraseToDisk(seedPhrase)
await showInfo({
message: `Your seed phrase was saved to INTERNAL storage, in the document folder. Keep that file secure.`,
message: isNative ? `Your seed phrase was downloaded by your browser.. Keep that file secure.` : `Your seed phrase was saved to INTERNAL storage, in the document folder. Keep that file secure.`,
})
} catch (error) {
@ -2705,7 +2705,7 @@ function App() {
fontWeight: 600,
}}
>
Download Wallet
Download Account
</TextP>
</Box>
<Spacer height="35px" />
@ -2735,10 +2735,10 @@ function App() {
<CustomButton onClick={async ()=> {
await saveFileToDiskFunc()
await showInfo({
message: `Your wallet file was saved to internal storage, in the document folder. Keep that file secure.`,
message: isNative ? `Your account file was saved to internal storage, in the document folder. Keep that file secure.` : `Your account file was downloaded by your browser. Keep that file secure.` ,
})
}}>
Download wallet
Download account
</CustomButton>
</>
)}
@ -2972,7 +2972,7 @@ await showInfo({
await saveFileToDiskFunc();
returnToMain();
await showInfo({
message: `Your wallet file was saved to internal storage, in the document folder. Keep that file secure.`,
message: isNative ? `Your account file was saved to internal storage, in the document folder. Keep that file secure.` : `Your account file was downloaded by your browser. Keep that file secure.`
})
}}
>

View File

@ -30,6 +30,7 @@ import { crypto } from "./constants/decryptWallet";
import { LoadingButton } from "@mui/lab";
import { PasswordField } from "./components";
import { FilePicker } from '@capawesome/capacitor-file-picker';
import { isNative } from "./components/Apps/useQortalMessageListener";
const parsefilenameQortal = (filename) => {
return filename.startsWith("qortal_backup_") ? filename.slice(14) : filename;
@ -119,6 +120,8 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
}
};
const { getRootProps, getInputProps } = useDropzone({
accept: {
"application/json": [".json"], // Only accept JSON files
@ -267,7 +270,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
</>
) : (
<>
<Typography>Your saved wallets</Typography>
<Typography>Your saved accounts</Typography>
</>
)}
</Box>
@ -328,6 +331,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
>
Add seed-phrase
</CustomButton>
{isNative ? (
<CustomButton
sx={{
padding: "10px",
@ -335,8 +339,17 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
onClick={handleFilePick}
>
Add wallets
Add account
</CustomButton>
) : (
<CustomButton sx={{
padding: '10px'
}} {...getRootProps()}>
<input {...getInputProps()} />
Add account
</CustomButton>
)}
</Box>
<Dialog

View File

@ -13,6 +13,8 @@ import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from "./constants/codes";
import ShortUniqueId from "short-unique-id";
import { App as CapacitorApp } from '@capacitor/app';
import Base58 from "./deps/Base58";
import ChatComputePowWorker from './chatComputePow.worker.js?worker';
import {
base64ToUint8Array,
decryptSingle,
@ -33,6 +35,8 @@ import NativePOW from './utils/nativepow'
import axios from 'axios'
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
import { Capacitor } from '@capacitor/core';
import {
addDataPublishesCase,
addEnteredQmailTimestampCase,
@ -405,11 +409,35 @@ function playNotificationSound() {
// chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" });
}
// const worker = new ChatComputePowWorker()
const worker = new ChatComputePowWorker()
export async function performPowTaskWeb(chatBytes, difficulty) {
return new Promise((resolve, reject) => {
worker.onmessage = (e) => {
if (e.data.error) {
reject(new Error(e.data.error));
} else {
resolve(e.data);
}
};
worker.onerror = (err) => {
reject(err);
};
// Send the task to the worker
worker.postMessage({
chatBytes,
path: `${import.meta.env.BASE_URL}memory-pow.wasm.full`,
difficulty,
});
});
}
export async function performPowTask(chatBytes, difficulty) {
const isNative = Capacitor.isNativePlatform();
const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
const result = await NativePOW.computeProofOfWork({ chatBytes, difficulty });
const result = isNative ? await NativePOW.computeProofOfWork({ chatBytes, difficulty }) : await performPowTaskWeb(chatBytes, difficulty);
return {nonce: result.nonce, chatBytesArray}
}

View File

@ -1,6 +1,6 @@
import { Sha256 } from 'asmcrypto.js';
import wasmInit from './memory-pow.wasm?init';
import NativePOW from './utils/nativepow'
let compute; // Exported compute function from Wasm
let memory; // WebAssembly.Memory instance
let heap; // Uint8Array view of the memory buffer
@ -24,7 +24,9 @@ async function loadWasm() {
const wasmModule = await wasmInit(importObject);
compute = wasmModule.exports.compute2;
console.log('Wasm loaded successfully:', compute);
} catch (error) {
console.error('Error loading Wasm:', error);
throw error;
}
}
@ -58,24 +60,24 @@ function sbrk(size) {
// Proof-of-Work computation function
async function computePow(chatBytes, difficulty) {
// if (!compute) {
// throw new Error('WebAssembly module not initialized. Call loadWasm first.');
// }
if (!compute) {
throw new Error('WebAssembly module not initialized. Call loadWasm first.');
}
// const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
// const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
// // Allocate memory for the hash
// const hashPtr = sbrk(32);
// const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
// hashAry.set(chatBytesHash);
// Allocate memory for the hash
const hashPtr = sbrk(32);
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
hashAry.set(chatBytesHash);
// // Reuse the work buffer if already allocated
// if (!workBufferPtr) {
// workBufferPtr = sbrk(workBufferLength);
// }
const nonce = await NativePOW.computeProofOfWork({ chatBytes, difficulty });
(hashPtr, workBufferPtr, workBufferLength, difficulty);
// Reuse the work buffer if already allocated
if (!workBufferPtr) {
workBufferPtr = sbrk(workBufferLength);
}
const nonce = compute(hashPtr, workBufferPtr, workBufferLength, difficulty);
return { nonce, chatBytesArray };
}
@ -86,9 +88,9 @@ self.addEventListener('message', async (e) => {
try {
// Initialize Wasm if not already done
// if (!compute) {
// await loadWasm();
// }
if (!compute) {
await loadWasm();
}
// Perform the POW computation
const result = await computePow(chatBytes, difficulty);

View File

@ -7,8 +7,11 @@ import { Browser } from '@capacitor/browser';
import { saveFile } from '../../qortalRequests/get';
import { mimeToExtensionMap } from '../../utils/memeTypes';
import { MyContext } from '../../App';
import FileSaver from 'file-saver';
import { Capacitor } from '@capacitor/core';
export const isNative = Capacitor.isNativePlatform();
export const saveFileInChunks = async (
@ -16,7 +19,7 @@ export const saveFileInChunks = async (
fileName: string,
chunkSize = 1024 * 1024
) => {
try {
let offset = 0;
let isFirstChunk = true;
@ -77,9 +80,7 @@ export const saveFileInChunks = async (
isFirstChunk = false;
}
} catch (error) {
throw error
}
};
@ -317,9 +318,11 @@ const UIQortalRequests = [
setOpenSnackGlobal(true);
if(isNative){
await saveFileInChunks(blob, filename)
} else {
FileSaver.saveAs(blob, filename)
}
setInfoSnackCustom({
type: "success",
message:

View File

@ -29,6 +29,7 @@ import SaveIcon from '@mui/icons-material/Save';
import { useSetRecoilState } from "recoil";
import { blobControllerAtom } from "../../atoms/global";
import { decodeIfEncoded } from "../../utils/decode";
import { isNative } from "../Apps/useQortalMessageListener";
export const AttachmentCard = ({
@ -67,7 +68,7 @@ export const AttachmentCard = ({
setInfoSnack({
type: "success",
message:
"File saved in INTERNAL STORAGE, DOCUMENT folder.",
isNative ? "File saved in INTERNAL STORAGE, DOCUMENT folder." : "File downloaded",
});
})
.catch(error => {
@ -126,7 +127,7 @@ export const AttachmentCard = ({
setInfoSnack({
type: "success",
message:
"File saved in INTERNAL STORAGE, DOCUMENT folder.",
isNative ? "File saved in INTERNAL STORAGE, DOCUMENT folder." : "File downloaded",
});
} catch (error) {
console.error(error)

View File

@ -383,7 +383,6 @@ export const ListOfGroupPromotions = () => {
<>
<Box
sx={{
width: '100%',
display: "flex",
flexDirection: "column",
padding: "0px 20px",

View File

@ -23,6 +23,7 @@ import { saveToLocalStorage } from "../Apps/AppsNavBar";
import { decryptData, encryptData } from "../../qortalRequests/get";
import { saveFileToDiskGeneric } from "../../utils/generateWallet/generateWallet";
import { base64ToUint8Array, uint8ArrayToObject } from "../../backgroundFunctions/encryption";
import { isNative } from "../Apps/useQortalMessageListener";
export const handleImportClick = async () => {
const fileInput = document.createElement('input');
@ -544,7 +545,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
await saveFileToDiskGeneric(blob, filename)
setInfoSnack({
type: "success",
message: "saved in INTERNAL storage under DOCUMENTS",
message: isNative ? "saved in INTERNAL storage under DOCUMENTS" : "file saved by your browser",
});
setOpenSnack(true);

View File

@ -9,6 +9,11 @@ import * as WORDLISTS from './wordlists';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import ShortUniqueId from "short-unique-id";
const uid = new ShortUniqueId({ length: 8 });
import FileSaver from 'file-saver';
import { Capacitor } from '@capacitor/core';
const isNative = Capacitor.isNativePlatform();
export function generateRandomSentence(template = 'adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun', maxWordLength = 0, capitalize = true) {
const partsOfSpeechMap = {
@ -89,7 +94,7 @@ export const createAccount = async(generatedSeedPhrase)=> {
}
export const saveFileToDisk = async (data: any, qortAddress: string) => {
if(isNative){
const dataString = JSON.stringify(data);
const fileName = `qortal_backup_${qortAddress}_${uid.rnd()}.json`;
@ -100,11 +105,19 @@ export const createAccount = async(generatedSeedPhrase)=> {
directory: Directory.Documents, // Save in the Documents folder
encoding: Encoding.UTF8,
});
} else {
const dataString = JSON.stringify(data);
const blob = new Blob([dataString], { type: 'application/json' });
const fileName = "qortal_backup_" + qortAddress + ".json";
await FileSaver.saveAs(blob, fileName);
}
};
export const saveSeedPhraseToDisk = async (data) => {
if(isNative){
const fileName = `qortal_seedphrase_${uid.rnd()}.txt`
await Filesystem.writeFile({
@ -113,7 +126,12 @@ export const saveSeedPhraseToDisk = async (data) => {
directory: Directory.Documents, // Save in the Documents folder
encoding: Encoding.UTF8,
});
} else {
const blob = new Blob([data], { type: 'text/plain;charset=utf-8' })
const fileName = "qortal_seedphrase.txt"
await FileSaver.saveAs(blob, fileName);
}
}
const hasExtension = (filename) => {
@ -122,6 +140,7 @@ const hasExtension = (filename) => {
export const saveFileToDiskGeneric = async (blob, filename) => {
if(isNative){
const timestamp = new Date()
.toISOString()
.replace(/:/g, "-"); // Safe timestamp for filenames
@ -130,5 +149,16 @@ export const saveFileToDiskGeneric = async (blob, filename) => {
let fileName = filename || "qortal_file_" + timestamp + "." + fileExtension;
fileName = hasExtension(fileName) ? fileName : fileName + "." + fileExtension;
await saveFileInChunks(blob, fileName)
// await FileSaver.saveAs(blob, fileName);
} else {
const timestamp = new Date()
.toISOString()
.replace(/:/g, "-"); // Safe timestamp for filenames
const fileExtension = mimeToExtensionMap[blob.type]
let fileName = filename || "qortal_file_" + timestamp + "." + fileExtension;
fileName = hasExtension(fileName) ? fileName : fileName + "." + fileExtension;
await FileSaver.saveAs(blob, fileName);
}
}

View File

@ -6,10 +6,39 @@ import { resolve } from 'path';
import fixReactVirtualized from 'esbuild-plugin-react-virtualized'
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
assetsInclude: ['**/*.wasm'],
plugins: [react(), wasm(), topLevelAwait()],
plugins: [react(), wasm(), topLevelAwait(), VitePWA({
registerType: 'prompt',
manifest: {
name: 'Qortal Go',
short_name: 'Go',
description: 'Your easy access to the Qortal blockchain',
start_url: '/',
display: 'standalone',
theme_color: '#ffffff',
background_color: '#ffffff',
icons: [
{
src: '/qortal192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/qortal.png',
sizes: '512x512',
type: 'image/png',
},
],
},
workbox: {
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB limit
disableDevLogs: true, // Suppresses logs in development
},
})],
build: {
rollupOptions: {
// Specify multiple entry points for Rollup