mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-04-25 04:17:53 +00:00
added pwa
This commit is contained in:
parent
e3d7d71c52
commit
299a42ca99
2249
package-lock.json
generated
2249
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
BIN
public/qortal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 158 KiB |
BIN
public/qortal192.png
Normal file
BIN
public/qortal192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
12
src/App.tsx
12
src/App.tsx
@ -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.`
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -383,7 +383,6 @@ export const ListOfGroupPromotions = () => {
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: "0px 20px",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user