mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-23 19:37: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"
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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
52
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
36
src/App.tsx
36
src/App.tsx
@ -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}
|
||||||
|
@ -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":
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
}
|
});
|
||||||
|
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user