added ability to download

This commit is contained in:
PhilReact 2024-10-30 19:11:03 +02:00
parent eb8049c12d
commit 457608b931
12 changed files with 376 additions and 103 deletions

View File

@ -9,8 +9,10 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-browser')
implementation project(':capacitor-filesystem')
implementation project(':evva-capacitor-secure-storage-plugin')
implementation "androidx.webkit:webkit:1.4.0"
} }

View File

@ -7,8 +7,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
android:requestLegacyExternalStorage="true">
<activity <activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:name=".MainActivity" android:name=".MainActivity"
@ -38,4 +38,6 @@
<!-- Permissions --> <!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest> </manifest>

View File

@ -1,3 +1,12 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/browser/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
include ':evva-capacitor-secure-storage-plugin'
project(':evva-capacitor-secure-storage-plugin').projectDir = new File('../node_modules/@evva/capacitor-secure-storage-plugin/android')

52
package-lock.json generated
View File

@ -12,11 +12,13 @@
"@capacitor/browser": "^6.0.3", "@capacitor/browser": "^6.0.3",
"@capacitor/cli": "^6.1.2", "@capacitor/cli": "^6.1.2",
"@capacitor/core": "^6.1.2", "@capacitor/core": "^6.1.2",
"@capacitor/filesystem": "^6.0.1",
"@chatscope/chat-ui-kit-react": "^2.0.3", "@chatscope/chat-ui-kit-react": "^2.0.3",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@evva/capacitor-secure-storage-plugin": "^3.0.1",
"@mui/icons-material": "^5.16.4", "@mui/icons-material": "^5.16.4",
"@mui/lab": "^5.0.0-alpha.173", "@mui/lab": "^5.0.0-alpha.173",
"@mui/material": "^5.16.7", "@mui/material": "^5.16.7",
@ -38,6 +40,8 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"buffer": "6.0.3", "buffer": "6.0.3",
"compressorjs": "^1.2.1", "compressorjs": "^1.2.1",
"cordova-plugin-android-permissions": "^1.1.5",
"cordova-plugin-file": "^8.1.1",
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"emoji-picker-react": "^4.12.0", "emoji-picker-react": "^4.12.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
@ -503,6 +507,14 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@capacitor/filesystem": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-6.0.1.tgz",
"integrity": "sha512-eHhXm6tzBIQhErzFnfOE6eA1U+15DHc2212/COfzzGGRk/dyGympoVV3ct2YPVzvpTSxMEW3xFocORv/xD9gFg==",
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@chatscope/chat-ui-kit-react": { "node_modules/@chatscope/chat-ui-kit-react": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@chatscope/chat-ui-kit-react/-/chat-ui-kit-react-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@chatscope/chat-ui-kit-react/-/chat-ui-kit-react-2.0.3.tgz",
@ -1186,6 +1198,14 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@evva/capacitor-secure-storage-plugin": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@evva/capacitor-secure-storage-plugin/-/capacitor-secure-storage-plugin-3.0.1.tgz",
"integrity": "sha512-6qupLfI+wIzozSAAz668aSddUjwhbaXFAlHUw1T4waAQjkWC/tRh2bfcLAHYB+MtQuWOrVI8uq65lnYHMac5SA==",
"peerDependencies": {
"@capacitor/core": "^6.1.2"
}
},
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
@ -4411,6 +4431,38 @@
"toggle-selection": "^1.0.6" "toggle-selection": "^1.0.6"
} }
}, },
"node_modules/cordova-plugin-android-permissions": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/cordova-plugin-android-permissions/-/cordova-plugin-android-permissions-1.1.5.tgz",
"integrity": "sha512-oTTV9cCMBqXTCmU+nYRebsP2IQfrtdvl2vYXHjoJgv5NHCIDgY4KFg6kJTcwXQOiymeGXuw0+MTvJJOueAdleA==",
"engines": [
{
"name": "cordova",
"version": ">=5.0.0"
}
]
},
"node_modules/cordova-plugin-file": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-8.1.1.tgz",
"integrity": "sha512-vrC9oC5rkKYbQDL5Y+K8l3z3dK5TAC88gwA9jScD5mZ0lwzPMGWcUF1Y8LXE0vtaRmPn/cKIdfRW+aB+QW8yKA==",
"engines": {
"cordovaDependencies": {
"5.0.0": {
"cordova-android": ">=6.3.0"
},
"7.0.0": {
"cordova-android": ">=10.0.0"
},
"8.0.0": {
"cordova-android": ">=12.0.0"
},
"9.0.0": {
"cordova": ">100"
}
}
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",

View File

@ -16,11 +16,13 @@
"@capacitor/browser": "^6.0.3", "@capacitor/browser": "^6.0.3",
"@capacitor/cli": "^6.1.2", "@capacitor/cli": "^6.1.2",
"@capacitor/core": "^6.1.2", "@capacitor/core": "^6.1.2",
"@capacitor/filesystem": "^6.0.1",
"@chatscope/chat-ui-kit-react": "^2.0.3", "@chatscope/chat-ui-kit-react": "^2.0.3",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@evva/capacitor-secure-storage-plugin": "^3.0.1",
"@mui/icons-material": "^5.16.4", "@mui/icons-material": "^5.16.4",
"@mui/lab": "^5.0.0-alpha.173", "@mui/lab": "^5.0.0-alpha.173",
"@mui/material": "^5.16.7", "@mui/material": "^5.16.7",
@ -42,6 +44,8 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"buffer": "6.0.3", "buffer": "6.0.3",
"compressorjs": "^1.2.1", "compressorjs": "^1.2.1",
"cordova-plugin-android-permissions": "^1.1.5",
"cordova-plugin-file": "^8.1.1",
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"emoji-picker-react": "^4.12.0", "emoji-picker-react": "^4.12.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",

View File

@ -331,6 +331,13 @@ function App() {
show: showUnsavedChanges, show: showUnsavedChanges,
message: messageUnsavedChanges, message: messageUnsavedChanges,
} = useModal(); } = useModal();
const {
isShow: isShowInfo,
onCancel: onCancelInfo,
onOk: onOkInfo,
show: showInfo,
message: messageInfo,
} = useModal();
const { const {
onCancel: onCancelQortalRequest, onCancel: onCancelQortalRequest,
@ -848,6 +855,9 @@ function App() {
walletToBeDownloaded.wallet, walletToBeDownloaded.wallet,
walletToBeDownloaded.qortAddress walletToBeDownloaded.qortAddress
); );
await showInfo({
message: `Your wallet file was saved to internal storage, in the document folder. Keep that file secure.`,
})
} catch (error: any) { } catch (error: any) {
setWalletToBeDownloadedError(error?.message); setWalletToBeDownloadedError(error?.message);
} finally { } finally {
@ -1520,6 +1530,7 @@ function App() {
show, show,
message, message,
rootHeight, rootHeight,
showInfo
}} }}
> >
<Box <Box
@ -2406,8 +2417,8 @@ function App() {
</TextP> </TextP>
<Spacer height="100px" /> <Spacer height="100px" />
<CustomButton <CustomButton
onClick={() => { onClick={async () => {
saveFileToDiskFunc(); await saveFileToDiskFunc();
returnToMain(); returnToMain();
}} }}
> >
@ -2547,6 +2558,27 @@ function App() {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
)} )}
{isShowInfo && (
<Dialog
open={isShow}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Important Info"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{message.message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onOk} autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
)}
{isShowUnsavedChanges && ( {isShowUnsavedChanges && (
<Dialog <Dialog
open={isShowUnsavedChanges} open={isShowUnsavedChanges}

View File

@ -1560,6 +1560,7 @@ const getStoredData = async (key) => {
export async function handleActiveGroupDataFromSocket({ groups, directs }) { export async function handleActiveGroupDataFromSocket({ groups, directs }) {
try { try {
console.log('handleActiveGroupDataFromSocket3', groups, directs)
window.postMessage({ window.postMessage({
action: "SET_GROUPS", action: "SET_GROUPS",
payload: groups, payload: groups,
@ -3024,6 +3025,7 @@ function setupMessageListener() {
publishOnQDNCase(request, event); publishOnQDNCase(request, event);
break; break;
case "handleActiveGroupDataFromSocket": case "handleActiveGroupDataFromSocket":
console.log('handleActiveGroupDataFromSocket2', event)
handleActiveGroupDataFromSocketCase(request, event); handleActiveGroupDataFromSocketCase(request, event);
break; break;
case "getThreadActivity": case "getThreadActivity":

View File

@ -1,10 +1,61 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import FileSaver from 'file-saver';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { navigationControllerAtom } from '../../atoms/global'; import { navigationControllerAtom } from '../../atoms/global';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { Browser } from '@capacitor/browser';
export const saveFileInChunks = async (blob: Blob, fileName: string, chunkSize = 1024 * 1024) => {
const base64Prefix = 'data:video/mp4;base64,';
try {
let offset = 0;
let isFirstChunk = true;
const fullFileName = fileName + Date.now() + '.mp4'
// Read the blob in chunks
while (offset < blob.size) {
// Extract the current chunk
const chunk = blob.slice(offset, offset + chunkSize);
// Convert the chunk to Base64
const base64Chunk = await blobToBase64(chunk);
// Write the chunk to the file with the prefix added on the first chunk
await Filesystem.writeFile({
path: fullFileName,
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
directory: Directory.Documents,
recursive: true,
append: !isFirstChunk // Append after the first chunk
});
// Update offset and flag
offset += chunkSize;
isFirstChunk = false;
}
console.log("File saved successfully in chunks:", fileName);
} catch (error) {
console.error("Error saving file in chunks:", error);
}
};
// Helper function to convert a Blob to a Base64 string
const blobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64data = reader.result?.toString().split(",")[1];
resolve(base64data || "");
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
};
class Semaphore { class Semaphore {
constructor(count) { constructor(count) {
this.count = count this.count = count
@ -166,34 +217,92 @@ const UIQortalRequests = [
} }
} }
export const showSaveFilePicker = async (data) => { export const showSaveFilePicker = async (data) => {
let blob let blob;
let fileName let fileName;
try { try {
const {filename, mimeType, fileHandleOptions, fileId} = data const { filename, mimeType, fileId } = data;
blob = await retrieveFileFromIndexedDB(fileId)
fileName = filename // Retrieve file from IndexedDB or any other source
blob = await retrieveFileFromIndexedDB(fileId);
fileName = filename;
await saveFileInChunks(blob, fileName)
} catch (error) {
console.error("Error saving file:", error);
}
};
const fileHandle = await window.showSaveFilePicker({ declare var cordova: any;
suggestedName: filename,
types: [
{
description: mimeType,
...fileHandleOptions
}
]
})
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
} catch (error) {
FileSaver.saveAs(blob, fileName)
}
}
// try {
// const { filename, mimeType, fileId } = data;
// // Request legacy storage permissions if applicable (for Android 12 and below)
// // await requestLegacyPermissions();
// // Retrieve file from IndexedDB or another source
// const blob = await retrieveFileFromIndexedDB(fileId);
// const buffer = await blob.arrayBuffer();
// return new Promise((resolve, reject) => {
// window.resolveLocalFileSystemURL(
// cordova.file.externalRootDirectory, // Points to the root of public external storage
// (rootDirectoryEntry) => {
// rootDirectoryEntry.getDirectory(
// "Downloads",
// { create: true },
// (downloadsDirectory) => {
// downloadsDirectory.getFile(
// filename,
// { create: true, exclusive: false },
// (fileEntry) => {
// fileEntry.createWriter((fileWriter) => {
// fileWriter.onwriteend = () => {
// console.log("Video saved successfully in public Downloads:", fileEntry.nativeURL);
// resolve(fileEntry.nativeURL);
// };
// fileWriter.onerror = (error) => {
// console.error("Error writing video file:", error);
// reject(error);
// };
// const videoBlob = new Blob([buffer], { type: mimeType || "video/mp4" });
// fileWriter.truncate(0);
// fileWriter.write(videoBlob);
// });
// },
// (error) => {
// console.error("Error accessing or creating file:", error);
// reject(error);
// }
// );
// },
// (error) => {
// console.error("Error accessing Downloads folder:", error);
// reject(error);
// }
// );
// },
// (error) => {
// console.error("Error accessing external storage:", error);
// reject(error);
// }
// );
// });
// } catch (error) {
// console.error("Error saving video file:", error);
// throw error;
// }
// };
async function storeFilesInIndexedDB(obj) { async function storeFilesInIndexedDB(obj) {
// First delete any existing files in IndexedDB with '_qortalfile' in their ID // First delete any existing files in IndexedDB with '_qortalfile' in their ID
await deleteQortalFilesFromIndexedDB(); await deleteQortalFilesFromIndexedDB();
@ -353,7 +462,6 @@ isDOMContentLoaded: false
) { ) {
let data; let data;
try { try {
console.log('storeFilesInIndexedDB', structuredClone(event.data))
data = await storeFilesInIndexedDB(event.data); data = await storeFilesInIndexedDB(event.data);
} catch (error) { } catch (error) {
console.error('Error storing files in IndexedDB:', error); console.error('Error storing files in IndexedDB:', error);

View File

@ -440,7 +440,7 @@ export const Group = ({
const [appsMode, setAppsMode] = useState('home') const [appsMode, setAppsMode] = useState('home')
const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false) const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false)
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false) const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
console.log('groups', groups)
const toggleSideViewDirects = ()=> { const toggleSideViewDirects = ()=> {
if(isOpenSideViewGroups){ if(isOpenSideViewGroups){
setIsOpenSideViewGroups(false) setIsOpenSideViewGroups(false)
@ -896,8 +896,10 @@ export const Group = ({
// Handler function for incoming messages // Handler function for incoming messages
const messageHandler = (event) => { const messageHandler = (event) => {
const message = event.data; const message = event.data;
console.log('SET_GROUPS100', event)
if (message?.action === "SET_GROUPS") { if (message?.action === "SET_GROUPS") {
console.log('SET_GROUPS200', event)
// Update the component state with the received 'sendqort' state // Update the component state with the received 'sendqort' state
setGroups(message.payload); setGroups(message.payload);
getLatestRegularChat(message.payload); getLatestRegularChat(message.payload);

View File

@ -72,7 +72,7 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
const sortedDirects = (data?.direct || []).filter(item => const sortedDirects = (data?.direct || []).filter(item =>
item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH'
).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); ).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
console.log('sortedGroups', sortedGroups)
window.sendMessage("handleActiveGroupDataFromSocket", { window.sendMessage("handleActiveGroupDataFromSocket", {
groups: sortedGroups, groups: sortedGroups,

View File

@ -1,54 +1,117 @@
import { SecureStoragePlugin } from '@evva/capacitor-secure-storage-plugin';
let inMemoryKey: CryptoKey | null = null;
let inMemoryIV: Uint8Array | null = null;
export const storeData = (key: string, payload: any): Promise<string> => { async function initializeKeyAndIV() {
return new Promise((resolve, reject) => { if (!inMemoryKey) {
try { inMemoryKey = await generateKey(); // Generates the key in memory
localStorage.setItem(key, JSON.stringify(payload));
resolve("Data saved successfully");
} catch (error) {
reject(new Error("Error saving data"));
}
});
};
export const getData = <T = any>(key: string): Promise<T> => {
return new Promise((resolve, reject) => {
try {
const data = localStorage.getItem(key);
if (data) {
resolve(JSON.parse(data) as T);
} else {
reject(new Error(`No data found for key: ${key}`));
}
} catch (error) {
reject(new Error("Error retrieving data"));
}
});
};
export async function removeKeysAndLogout(
keys: string[],
event: MessageEvent,
request: any
) {
try {
// Remove each key from localStorage
keys.forEach((key) => localStorage.removeItem(key));
// Send a response back to indicate successful logout
event.source.postMessage(
{
requestId: request.requestId,
action: "logout",
payload: true,
type: "backgroundMessageResponse",
},
event.origin
);
} catch (error) {
console.error("Error removing keys:", error);
}
} }
}
async function generateKey(): Promise<CryptoKey> {
return await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
}
async function encryptData(data: string, key: CryptoKey): Promise<{ iv: Uint8Array; encryptedData: ArrayBuffer }> {
const encoder = new TextEncoder();
const encodedData = encoder.encode(data);
// Generate a random IV each time you encrypt
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedData = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv
},
key,
encodedData
);
return { iv, encryptedData };
}
async function decryptData(encryptedData: ArrayBuffer, key: CryptoKey, iv: Uint8Array): Promise<string> {
const decryptedData = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
key,
encryptedData
);
const decoder = new TextDecoder();
return decoder.decode(decryptedData);
}
// Encrypt data, then concatenate the IV and encrypted data for storage
export const storeData = async (key: string, payload: any): Promise<string> => {
await initializeKeyAndIV();
if (inMemoryKey) {
const { iv, encryptedData } = await encryptData(JSON.stringify(payload), inMemoryKey);
// Combine IV and encrypted data into a single string
const combinedData = new Uint8Array([...iv, ...new Uint8Array(encryptedData)]);
await SecureStoragePlugin.set({ key, value: btoa(String.fromCharCode(...combinedData)) });
return "Data saved successfully";
} else {
throw new Error("Key is not initialized in memory");
}
};
// Retrieve data, split the IV and encrypted data, then decrypt
export const getData = async <T = any>(key: string): Promise<T> => {
await initializeKeyAndIV();
if (!inMemoryKey) throw new Error("Encryption key is not initialized");
const storedDataBase64 = await SecureStoragePlugin.get({ key });
if (storedDataBase64.value) {
const combinedData = atob(storedDataBase64.value).split("").map((c) => c.charCodeAt(0));
const iv = new Uint8Array(combinedData.slice(0, 12)); // First 12 bytes are the IV
const encryptedData = new Uint8Array(combinedData.slice(12)).buffer; // The rest is encrypted data
const decryptedData = await decryptData(encryptedData, inMemoryKey, iv);
return JSON.parse(decryptedData) as T;
} else {
throw new Error(`No data found for key: ${key}`);
}
};
// Remove keys from storage and log out
export async function removeKeysAndLogout(keys: string[], event: MessageEvent, request: any) {
try {
for (const key of keys) {
try {
await SecureStoragePlugin.remove({ key });
await SecureStoragePlugin.remove({ key: `${key}_iv` }); // Remove associated IV
} catch (error) {
console.warn(`Key not found: ${key}`);
}
}
event.source.postMessage(
{
requestId: request.requestId,
action: "logout",
payload: true,
type: "backgroundMessageResponse",
},
event.origin
);
} catch (error) {
console.error("Error removing keys:", error);
}
}

View File

@ -4,8 +4,7 @@ import { crypto, walletVersion } from '../../constants/decryptWallet';
import { doInitWorkers, kdf } from '../../deps/kdf'; import { doInitWorkers, kdf } from '../../deps/kdf';
import PhraseWallet from './phrase-wallet'; import PhraseWallet from './phrase-wallet';
import * as WORDLISTS from './wordlists'; import * as WORDLISTS from './wordlists';
import { saveAs } from 'file-saver'; import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
export function generateRandomSentence(template = 'adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun', maxWordLength = 0, capitalize = true) { export function generateRandomSentence(template = 'adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun', maxWordLength = 0, capitalize = true) {
const partsOfSpeechMap = { const partsOfSpeechMap = {
'noun': 'nouns', 'noun': 'nouns',
@ -84,19 +83,17 @@ export const createAccount = async()=> {
} }
export const saveFileToDisk = async (data, qortAddress) => { export const saveFileToDisk = async (data: any, qortAddress: string) => {
try {
const dataString = JSON.stringify(data);
const blob = new Blob([dataString], { type: 'application/json' });
const fileName = "qortal_backup_" + qortAddress + ".json";
saveAs(blob, fileName); const dataString = JSON.stringify(data);
} catch (error) { const fileName = `qortal_backup_${qortAddress}.json`;
if (error.name === 'AbortError') { // Write the file to the Filesystem
return; await Filesystem.writeFile({
} path: fileName,
// This fallback will only be executed if the `showSaveFilePicker` method fails. data: dataString,
FileSaver.saveAs(blob, fileName); // Ensure FileSaver is properly imported or available in your environment. directory: Directory.Documents, // Save in the Documents folder
} encoding: Encoding.UTF8,
} });
};