diff --git a/web/database/worker/db-worker.js b/web/database/worker/db-worker.js --- a/web/database/worker/db-worker.js +++ b/web/database/worker/db-worker.js @@ -148,6 +148,17 @@ async function processAppRequest( message: WorkerRequestMessage, ): Promise { + // testing + if (message.type === workerRequestMessageTypes.GET_DB_FILE) { + if (!dbModule) { + throw new Error(`Database not initialized, unable export database file`); + } + return { + type: workerResponseMessageTypes.GET_DB_FILE, + file: exportDatabaseContent(dbModule, COMM_SQLITE_DATABASE_PATH), + }; + } + // non-database operations if (message.type === workerRequestMessageTypes.PING) { return { diff --git a/web/settings/account-settings.react.js b/web/settings/account-settings.react.js --- a/web/settings/account-settings.react.js +++ b/web/settings/account-settings.react.js @@ -20,7 +20,9 @@ import TunnelbrokerTestScreen from './tunnelbroker-test.react.js'; import EditUserAvatar from '../avatars/edit-user-avatar.react.js'; import Button from '../components/button.react.js'; +import { getDatabaseModule } from '../database/database-module-provider.js'; import { useSelector } from '../redux/redux-utils.js'; +import { workerRequestMessageTypes } from '../types/worker-types.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; function AccountSettings(): React.Node { @@ -82,6 +84,27 @@ [popModal, pushModal], ); + const downloadDatabaseFile = React.useCallback(async () => { + const databaseModule = await getDatabaseModule(); + const response = await databaseModule.schedule({ + type: workerRequestMessageTypes.GET_DB_FILE, + }); + if (!response) { + return; + } + const { file } = response; + const blob = new Blob([file]); + const a = document.createElement('a'); + const url = URL.createObjectURL(blob); + a.href = url; + a.download = 'web.sqlite'; + + document.body?.appendChild(a); + a.click(); + document.body?.removeChild(a); + window.URL.revokeObjectURL(url); + }, []); + const showAppearanceModal = React.useCallback( () => pushModal(), [pushModal], @@ -170,6 +193,24 @@ ); } + let dbDownload; + if (staffCanSee) { + dbDownload = ( +
+

Database menu

+
+
    +
  • + Database file + +
  • +
+
+
+ ); + } return (
@@ -205,6 +246,7 @@ {preferences} {tunnelbroker} {backup} + {dbDownload}
); diff --git a/web/types/worker-types.js b/web/types/worker-types.js --- a/web/types/worker-types.js +++ b/web/types/worker-types.js @@ -20,6 +20,7 @@ REMOVE_PERSIST_STORAGE_ITEM: 9, CLEAR_SENSITIVE_DATA: 10, BACKUP_RESTORE: 11, + GET_DB_FILE: 99999, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -93,6 +94,10 @@ +backupLogDataKey: string, }; +export type GetDBFileRequestMessage = { + +type: 99999, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -105,7 +110,8 @@ | SetPersistStorageItemRequestMessage | RemovePersistStorageItemRequestMessage | ClearSensitiveDataRequestMessage - | BackupRestoreRequestMessage; + | BackupRestoreRequestMessage + | GetDBFileRequestMessage; export type WorkerRequestProxyMessage = { +id: number, @@ -118,6 +124,7 @@ CLIENT_STORE: 1, GET_CURRENT_USER_ID: 2, GET_PERSIST_STORAGE_ITEM: 3, + GET_DB_FILE: 99999, }); export type PongWorkerResponseMessage = { @@ -140,11 +147,17 @@ +item: string, }; +export type GetDBFileResponseMessage = { + +type: 99999, + +file: Uint8Array, +}; + export type WorkerResponseMessage = | PongWorkerResponseMessage | ClientStoreResponseMessage | GetCurrentUserIDResponseMessage - | GetPersistStorageItemResponseMessage; + | GetPersistStorageItemResponseMessage + | GetDBFileResponseMessage; export type WorkerResponseProxyMessage = { +id?: number,