Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions packages/mobile/src/Lib/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { hide, show } from 'react-native-privacy-snapshot'
import Share from 'react-native-share'
import { AppStateObserverService } from './../AppStateObserverService'
import Keychain from './Keychain'
import { SNReactNativeCrypto } from './ReactNativeCrypto'
import { IsMobileWeb } from './Utils'

export type BiometricsType = 'Fingerprint' | 'Face ID' | 'Biometrics' | 'Touch ID'
Expand Down Expand Up @@ -79,14 +78,11 @@ export class MobileDevice implements MobileDeviceInterface {
platform: SNPlatform.Ios | SNPlatform.Android = Platform.OS === 'ios' ? SNPlatform.Ios : SNPlatform.Android
private eventObservers: MobileDeviceEventHandler[] = []
public isDarkMode = false
private crypto: SNReactNativeCrypto

constructor(
private stateObserverService?: AppStateObserverService,
private androidBackHandlerService?: AndroidBackHandlerService,
) {
this.crypto = new SNReactNativeCrypto()
}
) {}

deinit() {
this.stateObserverService?.deinit()
Expand Down Expand Up @@ -541,8 +537,7 @@ export class MobileDevice implements MobileDeviceInterface {
try {
const path = this.getFileDestinationPath(filename, saveInTempLocation)
void this.deleteFileAtPathIfExists(path)
const decodedContents = this.crypto.base64Decode(base64.replace(/data.*base64,/, ''))
await writeFile(path, decodedContents)
await writeFile(path, base64.replace(/data.*base64,/, ''), 'base64')
return path
} catch (error) {
this.consoleLog(`${error}`)
Expand Down
20 changes: 8 additions & 12 deletions packages/ui-services/src/Archive/ArchiveManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class ArchiveManager {
return string
}

private async downloadZippedDecryptedItems(data: BackupFile) {
async getZippedDecryptedItemsBlob(data: BackupFile) {
const zip = await import('@zip.js/zip.js')
const zipWriter = new zip.ZipWriter(new zip.BlobWriter('application/zip'))
const items = data.items
Expand All @@ -83,8 +83,7 @@ export class ArchiveManager {
const fileName = zippableFileName('Standard Notes Backup and Import File')
await zipWriter.add(fileName, new zip.BlobReader(blob))

let index = 0
const nextFile = async () => {
for (let index = 0; index < items.length; index++) {
const item = items[index]
let name, contents

Expand All @@ -105,17 +104,14 @@ export class ArchiveManager {
const fileName =
`Items/${sanitizeFileName(item.content_type)}/` + zippableFileName(name, `-${item.uuid.split('-')[0]}`)
await zipWriter.add(fileName, new zip.BlobReader(blob))

index++
if (index < items.length) {
await nextFile()
} else {
const finalBlob = await zipWriter.close()
this.downloadData(finalBlob, `Standard Notes Backup - ${this.formattedDateForExports()}.zip`)
}
}

await nextFile()
return await zipWriter.close()
}

private async downloadZippedDecryptedItems(data: BackupFile) {
const zippedDecryptedItemsBlob = await this.getZippedDecryptedItemsBlob(data)
this.downloadData(zippedDecryptedItemsBlob, `Standard Notes Backup - ${this.formattedDateForExports()}.zip`)
}

async zipData(data: ZippableData): Promise<Blob> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { formatDateForContextMenu } from '@/Utils/DateUtils'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { getNoteBlob, getNoteFileName } from '@/Utils/NoteExportUtils'
import { shareSelectedItems } from '@/NativeMobileWeb/ShareSelectedItems'
import { downloadSelectedItemsOnAndroid } from '@/NativeMobileWeb/DownloadSelectedItemsOnAndroid'
import { shareSelectedNotes } from '@/NativeMobileWeb/ShareSelectedNotes'
import { downloadSelectedNotesOnAndroid } from '@/NativeMobileWeb/DownloadSelectedNotesOnAndroid'

type DeletePermanentlyButtonProps = {
onClick: () => void
Expand Down Expand Up @@ -355,7 +355,7 @@ const NotesOptions = ({
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={() => {
application.isNativeMobileWeb() ? shareSelectedItems(application, notes) : downloadSelectedItems()
application.isNativeMobileWeb() ? shareSelectedNotes(application, notes) : downloadSelectedItems()
}}
>
<Icon type={application.platform === Platform.Android ? 'share' : 'download'} className={iconClass} />
Expand All @@ -364,7 +364,7 @@ const NotesOptions = ({
{application.platform === Platform.Android && (
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
onClick={() => downloadSelectedItemsOnAndroid(application, notes)}
onClick={() => downloadSelectedNotesOnAndroid(application, notes)}
>
<Icon type="download" className={iconClass} />
Export
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isDesktopApplication } from '@/Utils'
import { alertDialog } from '@standardnotes/ui-services'
import { alertDialog, sanitizeFileName } from '@standardnotes/ui-services'
import {
STRING_IMPORT_SUCCESS,
STRING_INVALID_IMPORT_FILE,
Expand All @@ -21,6 +21,7 @@ import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Spinner from '@/Components/Spinner/Spinner'
import { downloadOrShareBlobBasedOnPlatform } from '@/Utils/DownloadOrShareBasedOnPlatform'

type Props = {
application: WebApplication
Expand Down Expand Up @@ -59,8 +60,31 @@ const DataBackups = ({ application, viewControllerManager }: Props) => {
refreshEncryptionStatus()
}, [refreshEncryptionStatus])

const downloadDataArchive = () => {
application.getArchiveService().downloadBackup(isBackupEncrypted).catch(console.error)
const downloadDataArchive = async () => {
const data = isBackupEncrypted
? await application.createEncryptedBackupFile()
: await application.createDecryptedBackupFile()

if (!data) {
return
}

const blobData = new Blob([JSON.stringify(data, null, 2)], {
type: 'text/json',
})

if (isBackupEncrypted) {
const filename = `Standard Notes Encrypted Backup and Import File - ${application
.getArchiveService()
.formattedDateForExports()}`
const sanitizedFilename = sanitizeFileName(filename) + '.txt'
downloadOrShareBlobBasedOnPlatform(application, blobData, sanitizedFilename)
} else {
const zippedDecryptedItemsBlob = await application.getArchiveService().getZippedDecryptedItemsBlob(data)
const filename = `Standard Notes Backup - ${application.getArchiveService().formattedDateForExports()}`
const sanitizedFilename = sanitizeFileName(filename) + '.zip'
downloadOrShareBlobBasedOnPlatform(application, zippedDecryptedItemsBlob, sanitizedFilename)
}
}

const readFile = async (file: File): Promise<any> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { WebApplication } from '@/Application/Application'
import { getBase64FromBlob } from '@/Utils'
import { Platform } from '@standardnotes/snjs'
import { addToast, ToastType, dismissToast } from '@standardnotes/toast'

export const downloadBlobOnAndroid = async (application: WebApplication, blob: Blob, filename: string) => {
if (!application.isNativeMobileWeb() || application.platform !== Platform.Android) {
throw new Error('Download function being used on non-android platform')
}
const loadingToastId = addToast({
type: ToastType.Loading,
message: `Downloading ${filename}..`,
})
const base64 = await getBase64FromBlob(blob)
const downloaded = await application.mobileDevice.downloadBase64AsFile(base64, filename)
if (downloaded) {
dismissToast(loadingToastId)
addToast({
type: ToastType.Success,
message: `Downloaded ${filename}`,
})
} else {
addToast({
type: ToastType.Error,
message: `Could not download ${filename}`,
})
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { WebApplication } from '@/Application/Application'
import { getNoteBlob, getNoteFileName } from '@/Utils/NoteExportUtils'
import { parseFileName } from '@standardnotes/filepicker'
import { Platform, SNNote } from '@standardnotes/snjs'
import { sanitizeFileName } from '@standardnotes/ui-services'
import { downloadBlobOnAndroid } from './DownloadBlobOnAndroid'

export const downloadSelectedNotesOnAndroid = async (application: WebApplication, notes: SNNote[]) => {
if (!application.isNativeMobileWeb() || application.platform !== Platform.Android) {
throw new Error('Function being used on non-android platform')
}
if (notes.length === 1) {
const note = notes[0]
const blob = getNoteBlob(application, note)
const { name, ext } = parseFileName(getNoteFileName(application, note))
const filename = `${sanitizeFileName(name)}.${ext}`
await downloadBlobOnAndroid(application, blob, filename)
return
}
if (notes.length > 1) {
const zippedDataBlob = await application.getArchiveService().zipData(
notes.map((note) => {
return {
name: getNoteFileName(application, note),
content: getNoteBlob(application, note),
}
}),
)
const filename = `Standard Notes Export - ${application.getArchiveService().formattedDateForExports()}.zip`
await downloadBlobOnAndroid(application, zippedDataBlob, filename)
}
}
10 changes: 10 additions & 0 deletions packages/web/src/javascripts/NativeMobileWeb/ShareBlobOnMobile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { WebApplication } from '@/Application/Application'
import { getBase64FromBlob } from '@/Utils'

export const shareBlobOnMobile = async (application: WebApplication, blob: Blob, filename: string) => {
if (!application.isNativeMobileWeb()) {
throw new Error('Share function being used outside mobile webview')
}
const base64 = await getBase64FromBlob(blob)
application.mobileDevice.shareBase64AsFile(base64, filename)
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { WebApplication } from '@/Application/Application'
import { getBase64FromBlob } from '@/Utils'
import { getNoteBlob, getNoteFileName } from '@/Utils/NoteExportUtils'
import { parseFileName } from '@standardnotes/filepicker'
import { SNNote } from '@standardnotes/snjs'
import { sanitizeFileName } from '@standardnotes/ui-services'
import { shareBlobOnMobile } from './ShareBlobOnMobile'

export const shareSelectedItems = async (application: WebApplication, notes: SNNote[]) => {
export const shareSelectedNotes = async (application: WebApplication, notes: SNNote[]) => {
if (!application.isNativeMobileWeb()) {
throw new Error('Share function being used outside mobile webview')
}
if (notes.length === 1) {
const note = notes[0]
const blob = getNoteBlob(application, note)
const base64 = await getBase64FromBlob(blob)
const { name, ext } = parseFileName(getNoteFileName(application, note))
const filename = `${sanitizeFileName(name)}.${ext}`
application.mobileDevice.shareBase64AsFile(base64, filename)
void shareBlobOnMobile(application, blob, filename)
return
}
if (notes.length > 1) {
Expand All @@ -27,9 +26,9 @@ export const shareSelectedItems = async (application: WebApplication, notes: SNN
}
}),
)
const zippedDataAsBase64 = await getBase64FromBlob(zippedDataBlob)
application.mobileDevice.shareBase64AsFile(
zippedDataAsBase64,
void shareBlobOnMobile(
application,
zippedDataBlob,
`Standard Notes Export - ${application.getArchiveService().formattedDateForExports()}.zip`,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { WebApplication } from '@/Application/Application'
import { downloadBlobOnAndroid } from '@/NativeMobileWeb/DownloadBlobOnAndroid'
import { shareBlobOnMobile } from '@/NativeMobileWeb/ShareBlobOnMobile'
import { Platform } from '@standardnotes/snjs'

export const downloadOrShareBlobBasedOnPlatform = async (application: WebApplication, blob: Blob, filename: string) => {
if (!application.isNativeMobileWeb()) {
application.getArchiveService().downloadData(blob, filename)
return
}

if (application.platform === Platform.Ios) {
shareBlobOnMobile(application, blob, filename)
return
}

if (application.platform === Platform.Android) {
downloadBlobOnAndroid(application, blob, filename)
return
}
}