mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-25 04:17:52 +00:00
added ability to download
This commit is contained in:
parent
eb8049c12d
commit
457608b931
@ -9,8 +9,10 @@ android {
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
|
||||
|
||||
implementation project(':capacitor-browser')
|
||||
implementation project(':capacitor-filesystem')
|
||||
implementation project(':evva-capacitor-secure-storage-plugin')
|
||||
implementation "androidx.webkit:webkit:1.4.0"
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,8 +7,8 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
android:theme="@style/AppTheme"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name=".MainActivity"
|
||||
@ -38,4 +38,6 @@
|
||||
<!-- Permissions -->
|
||||
|
||||
<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>
|
||||
|
@ -1,3 +1,12 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
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
52
package-lock.json
generated
@ -12,11 +12,13 @@
|
||||
"@capacitor/browser": "^6.0.3",
|
||||
"@capacitor/cli": "^6.1.2",
|
||||
"@capacitor/core": "^6.1.2",
|
||||
"@capacitor/filesystem": "^6.0.1",
|
||||
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@evva/capacitor-secure-storage-plugin": "^3.0.1",
|
||||
"@mui/icons-material": "^5.16.4",
|
||||
"@mui/lab": "^5.0.0-alpha.173",
|
||||
"@mui/material": "^5.16.7",
|
||||
@ -38,6 +40,8 @@
|
||||
"bcryptjs": "2.4.3",
|
||||
"buffer": "6.0.3",
|
||||
"compressorjs": "^1.2.1",
|
||||
"cordova-plugin-android-permissions": "^1.1.5",
|
||||
"cordova-plugin-file": "^8.1.1",
|
||||
"dompurify": "^3.1.6",
|
||||
"emoji-picker-react": "^4.12.0",
|
||||
"file-saver": "^2.0.5",
|
||||
@ -503,6 +507,14 @@
|
||||
"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": {
|
||||
"version": "2.0.3",
|
||||
"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_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": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
||||
@ -4411,6 +4431,38 @@
|
||||
"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": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
|
@ -16,11 +16,13 @@
|
||||
"@capacitor/browser": "^6.0.3",
|
||||
"@capacitor/cli": "^6.1.2",
|
||||
"@capacitor/core": "^6.1.2",
|
||||
"@capacitor/filesystem": "^6.0.1",
|
||||
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@evva/capacitor-secure-storage-plugin": "^3.0.1",
|
||||
"@mui/icons-material": "^5.16.4",
|
||||
"@mui/lab": "^5.0.0-alpha.173",
|
||||
"@mui/material": "^5.16.7",
|
||||
@ -42,6 +44,8 @@
|
||||
"bcryptjs": "2.4.3",
|
||||
"buffer": "6.0.3",
|
||||
"compressorjs": "^1.2.1",
|
||||
"cordova-plugin-android-permissions": "^1.1.5",
|
||||
"cordova-plugin-file": "^8.1.1",
|
||||
"dompurify": "^3.1.6",
|
||||
"emoji-picker-react": "^4.12.0",
|
||||
"file-saver": "^2.0.5",
|
||||
|
36
src/App.tsx
36
src/App.tsx
@ -331,6 +331,13 @@ function App() {
|
||||
show: showUnsavedChanges,
|
||||
message: messageUnsavedChanges,
|
||||
} = useModal();
|
||||
const {
|
||||
isShow: isShowInfo,
|
||||
onCancel: onCancelInfo,
|
||||
onOk: onOkInfo,
|
||||
show: showInfo,
|
||||
message: messageInfo,
|
||||
} = useModal();
|
||||
|
||||
const {
|
||||
onCancel: onCancelQortalRequest,
|
||||
@ -848,6 +855,9 @@ function App() {
|
||||
walletToBeDownloaded.wallet,
|
||||
walletToBeDownloaded.qortAddress
|
||||
);
|
||||
await showInfo({
|
||||
message: `Your wallet file was saved to internal storage, in the document folder. Keep that file secure.`,
|
||||
})
|
||||
} catch (error: any) {
|
||||
setWalletToBeDownloadedError(error?.message);
|
||||
} finally {
|
||||
@ -1520,6 +1530,7 @@ function App() {
|
||||
show,
|
||||
message,
|
||||
rootHeight,
|
||||
showInfo
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -2406,8 +2417,8 @@ function App() {
|
||||
</TextP>
|
||||
<Spacer height="100px" />
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
saveFileToDiskFunc();
|
||||
onClick={async () => {
|
||||
await saveFileToDiskFunc();
|
||||
returnToMain();
|
||||
}}
|
||||
>
|
||||
@ -2547,6 +2558,27 @@ function App() {
|
||||
</DialogActions>
|
||||
</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 && (
|
||||
<Dialog
|
||||
open={isShowUnsavedChanges}
|
||||
|
@ -1560,6 +1560,7 @@ const getStoredData = async (key) => {
|
||||
|
||||
export async function handleActiveGroupDataFromSocket({ groups, directs }) {
|
||||
try {
|
||||
console.log('handleActiveGroupDataFromSocket3', groups, directs)
|
||||
window.postMessage({
|
||||
action: "SET_GROUPS",
|
||||
payload: groups,
|
||||
@ -3024,6 +3025,7 @@ function setupMessageListener() {
|
||||
publishOnQDNCase(request, event);
|
||||
break;
|
||||
case "handleActiveGroupDataFromSocket":
|
||||
console.log('handleActiveGroupDataFromSocket2', event)
|
||||
handleActiveGroupDataFromSocketCase(request, event);
|
||||
break;
|
||||
case "getThreadActivity":
|
||||
|
@ -1,10 +1,61 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import FileSaver from 'file-saver';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
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 {
|
||||
constructor(count) {
|
||||
this.count = count
|
||||
@ -166,33 +217,91 @@ const UIQortalRequests = [
|
||||
}
|
||||
}
|
||||
|
||||
export const showSaveFilePicker = async (data) => {
|
||||
let blob
|
||||
let fileName
|
||||
try {
|
||||
const {filename, mimeType, fileHandleOptions, fileId} = data
|
||||
blob = await retrieveFileFromIndexedDB(fileId)
|
||||
fileName = filename
|
||||
|
||||
const fileHandle = await window.showSaveFilePicker({
|
||||
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"))
|
||||
|
||||
|
||||
export const showSaveFilePicker = async (data) => {
|
||||
let blob;
|
||||
let fileName;
|
||||
|
||||
try {
|
||||
const { filename, mimeType, fileId } = data;
|
||||
|
||||
// Retrieve file from IndexedDB or any other source
|
||||
blob = await retrieveFileFromIndexedDB(fileId);
|
||||
fileName = filename;
|
||||
|
||||
await saveFileInChunks(blob, fileName)
|
||||
} catch (error) {
|
||||
FileSaver.saveAs(blob, fileName)
|
||||
}
|
||||
console.error("Error saving file:", error);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
declare var cordova: any;
|
||||
|
||||
|
||||
// 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) {
|
||||
// First delete any existing files in IndexedDB with '_qortalfile' in their ID
|
||||
@ -353,7 +462,6 @@ isDOMContentLoaded: false
|
||||
) {
|
||||
let data;
|
||||
try {
|
||||
console.log('storeFilesInIndexedDB', structuredClone(event.data))
|
||||
data = await storeFilesInIndexedDB(event.data);
|
||||
} catch (error) {
|
||||
console.error('Error storing files in IndexedDB:', error);
|
||||
|
@ -440,7 +440,7 @@ export const Group = ({
|
||||
const [appsMode, setAppsMode] = useState('home')
|
||||
const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false)
|
||||
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
|
||||
|
||||
console.log('groups', groups)
|
||||
const toggleSideViewDirects = ()=> {
|
||||
if(isOpenSideViewGroups){
|
||||
setIsOpenSideViewGroups(false)
|
||||
@ -896,8 +896,10 @@ export const Group = ({
|
||||
// Handler function for incoming messages
|
||||
const messageHandler = (event) => {
|
||||
const message = event.data;
|
||||
|
||||
console.log('SET_GROUPS100', event)
|
||||
if (message?.action === "SET_GROUPS") {
|
||||
console.log('SET_GROUPS200', event)
|
||||
|
||||
// Update the component state with the received 'sendqort' state
|
||||
setGroups(message.payload);
|
||||
getLatestRegularChat(message.payload);
|
||||
|
@ -72,7 +72,7 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
|
||||
const sortedDirects = (data?.direct || []).filter(item =>
|
||||
item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH'
|
||||
).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
||||
|
||||
console.log('sortedGroups', sortedGroups)
|
||||
|
||||
window.sendMessage("handleActiveGroupDataFromSocket", {
|
||||
groups: sortedGroups,
|
||||
|
@ -1,43 +1,107 @@
|
||||
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> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(payload));
|
||||
resolve("Data saved successfully");
|
||||
} catch (error) {
|
||||
reject(new Error("Error saving data"));
|
||||
async function initializeKeyAndIV() {
|
||||
if (!inMemoryKey) {
|
||||
inMemoryKey = await generateKey(); // Generates the key in memory
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
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 {
|
||||
reject(new Error(`No data found for key: ${key}`));
|
||||
throw new Error("Key is not initialized in memory");
|
||||
}
|
||||
} catch (error) {
|
||||
reject(new Error("Error retrieving data"));
|
||||
};
|
||||
|
||||
// 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}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export async function removeKeysAndLogout(
|
||||
keys: string[],
|
||||
event: MessageEvent,
|
||||
request: any
|
||||
) {
|
||||
// Remove keys from storage and log out
|
||||
export async function removeKeysAndLogout(keys: string[], event: MessageEvent, request: any) {
|
||||
try {
|
||||
// Remove each key from localStorage
|
||||
keys.forEach((key) => localStorage.removeItem(key));
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Send a response back to indicate successful logout
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
@ -51,4 +115,3 @@ export const storeData = (key: string, payload: any): Promise<string> => {
|
||||
console.error("Error removing keys:", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,7 @@ import { crypto, walletVersion } from '../../constants/decryptWallet';
|
||||
import { doInitWorkers, kdf } from '../../deps/kdf';
|
||||
import PhraseWallet from './phrase-wallet';
|
||||
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) {
|
||||
const partsOfSpeechMap = {
|
||||
'noun': 'nouns',
|
||||
@ -84,19 +83,17 @@ export const createAccount = async()=> {
|
||||
|
||||
}
|
||||
|
||||
export const saveFileToDisk = async (data, qortAddress) => {
|
||||
try {
|
||||
export const saveFileToDisk = async (data: any, qortAddress: string) => {
|
||||
|
||||
const dataString = JSON.stringify(data);
|
||||
const blob = new Blob([dataString], { type: 'application/json' });
|
||||
const fileName = "qortal_backup_" + qortAddress + ".json";
|
||||
const fileName = `qortal_backup_${qortAddress}.json`;
|
||||
|
||||
saveAs(blob, fileName);
|
||||
} catch (error) {
|
||||
// Write the file to the Filesystem
|
||||
await Filesystem.writeFile({
|
||||
path: fileName,
|
||||
data: dataString,
|
||||
directory: Directory.Documents, // Save in the Documents folder
|
||||
encoding: Encoding.UTF8,
|
||||
});
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// This fallback will only be executed if the `showSaveFilePicker` method fails.
|
||||
FileSaver.saveAs(blob, fileName); // Ensure FileSaver is properly imported or available in your environment.
|
||||
}
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user