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
30 changes: 30 additions & 0 deletions packages/services/src/Domain/Strings/Messages.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ValetTokenOperation } from '@standardnotes/responses'
import { FileStrings } from './Messages'

describe('FileStrings.ValetTokenFailureMessage', () => {
it('returns a clear message for expired subscription on download', () => {
const message = FileStrings.ValetTokenFailureMessage('expired-subscription', ValetTokenOperation.Read)
expect(message).toContain('subscription has expired')
expect(message).toContain('downloaded')
expect(message).not.toContain('expired-subscription')
expect(message).not.toContain('Account settings')
})

it('returns a clear message for expired subscription on upload', () => {
const message = FileStrings.ValetTokenFailureMessage('expired-subscription', ValetTokenOperation.Write)
expect(message).toContain('subscription has expired')
expect(message).toContain('uploaded')
})

it('returns a clear message for missing subscription', () => {
const message = FileStrings.ValetTokenFailureMessage('no-subscription', ValetTokenOperation.Read)
expect(message).toContain('active subscription')
expect(message).not.toContain('Account settings')
expect(message).not.toContain('no-subscription')
})

it('falls back to generic file token error for unknown reasons', () => {
const message = FileStrings.ValetTokenFailureMessage('something-else')
expect(message).toContain('Failed to create file token')
})
})
42 changes: 39 additions & 3 deletions packages/services/src/Domain/Strings/Messages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ProtocolVersion } from '@standardnotes/models'
import { CreateValetTokenResponseData, ValetTokenOperation } from '@standardnotes/responses'
import { c } from 'ttag'
import { jtString, AppName, ListedName, HelpSecurityUrl, SupportEmail } from '@standardnotes/features'

Expand Down Expand Up @@ -49,9 +50,6 @@ export const API_MESSAGE_FAILED_UPDATE_SETTINGS = () => c('B6.Preferences.Error'
export const API_MESSAGE_FAILED_LISTED_REGISTRATION = () =>
jtString(c('B6.Preferences.Error').jt`Unable to register for ${ListedName}. Please try again later.`)

export const API_MESSAGE_FAILED_CREATE_FILE_TOKEN = () =>
c('B7.FilesSubscriptionHelp.Files.Error').t`Failed to create file token.`

export const API_MESSAGE_FAILED_SUBSCRIPTION_INFO = () =>
c('B7.FilesSubscriptionHelp.Subscription.Error').t`Failed to get subscription's information.`

Expand Down Expand Up @@ -319,6 +317,44 @@ export const ChallengeStrings = {
},
}

type CreateValetTokenFailureReason = Extract<CreateValetTokenResponseData, { success: false }>['reason']

export const FileStrings = {
get FailedCreateFileToken() {
return c('B7.FilesSubscriptionHelp.Files.Error').t`Failed to create file token.`
},

ValetTokenFailureMessage(reason: CreateValetTokenFailureReason | string, operation?: ValetTokenOperation): string {
switch (reason) {
case 'expired-subscription':
if (operation === ValetTokenOperation.Write) {
return c('B7.FilesSubscriptionHelp.Files.Error')
.t`Files cannot be uploaded because your subscription has expired. Renew your subscription to use encrypted file storage again.`
}
if (operation === ValetTokenOperation.Read) {
return c('B7.FilesSubscriptionHelp.Files.Error')
.t`This file cannot be downloaded because your subscription has expired. Renew your subscription to access your files again.`
}
return c('B7.FilesSubscriptionHelp.Files.Error')
.t`Encrypted files are unavailable because your subscription has expired. Renew your subscription to download and upload files again.`
case 'no-subscription':
if (operation === ValetTokenOperation.Write) {
return c('B7.FilesSubscriptionHelp.Files.Error')
.t`Files cannot be uploaded without an active subscription. Upgrade your plan to use encrypted file storage.`
}
if (operation === ValetTokenOperation.Read) {
return c('B7.FilesSubscriptionHelp.Files.Error')
.t`This file cannot be downloaded without an active subscription. Upgrade your plan to access encrypted files.`
}
return c('B7.FilesSubscriptionHelp.Files.Error')
.t`Encrypted file storage requires an active subscription. Upgrade your plan to download and upload files.`
case 'invalid-parameters':
default:
return FileStrings.FailedCreateFileToken
}
},
}

export const ErrorAlertStrings = {
get MissingSessionTitle() {
return c('B1.Account.Session.Error').t`Missing Session`
Expand Down
7 changes: 4 additions & 3 deletions packages/snjs/lib/Services/Api/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
API_MESSAGE_GENERIC_TOKEN_REFRESH_FAIL,
API_MESSAGE_CHANGE_CREDENTIALS_IN_PROGRESS,
API_MESSAGE_FAILED_ACCESS_PURCHASE,
API_MESSAGE_FAILED_CREATE_FILE_TOKEN,
FileStrings,
API_MESSAGE_FAILED_GET_SETTINGS,
API_MESSAGE_FAILED_LISTED_REGISTRATION,
API_MESSAGE_FAILED_OFFLINE_ACTIVATION,
Expand Down Expand Up @@ -739,7 +739,7 @@ export class LegacyApiService
verb: HttpVerb.Post,
url: url,
authentication: this.getSessionAccessToken(),
fallbackErrorMessage: API_MESSAGE_FAILED_CREATE_FILE_TOKEN(),
fallbackErrorMessage: FileStrings.FailedCreateFileToken,
params,
})

Expand All @@ -748,7 +748,8 @@ export class LegacyApiService
}

if (!response.data?.success) {
return new ClientDisplayableError(response.data?.reason as string, undefined, response.data?.reason as string)
const reason = response.data.reason
return new ClientDisplayableError(FileStrings.ValetTokenFailureMessage(reason, operation), undefined, reason)
}

return response.data?.valetToken
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const FilePreview = ({
const [isDownloading, setIsDownloading] = useState(true)
const [downloadProgress, setDownloadProgress] = useState<FileDownloadProgress | undefined>()
const [downloadedBytes, setDownloadedBytes] = useState<Uint8Array>()
const [downloadErrorMessage, setDownloadErrorMessage] = useState<string | undefined>()

useEffect(() => {
setIsAuthorized(application.isAuthorizedToRenderItem(file))
Expand Down Expand Up @@ -75,14 +76,17 @@ const FilePreview = ({
try {
const chunks: Uint8Array[] = []
setDownloadProgress(undefined)
setDownloadErrorMessage(undefined)
const error = await application.files.downloadFile(file, async (decryptedChunk, progress) => {
chunks.push(decryptedChunk)
if (progress) {
setDownloadProgress(progress)
}
})

if (!error) {
if (error) {
setDownloadErrorMessage(error.text)
} else {
const finalDecryptedBytes = concatenateUint8Arrays(chunks)
setDownloadedBytes(finalDecryptedBytes)
}
Expand Down Expand Up @@ -159,8 +163,10 @@ const FilePreview = ({
filesController={application.filesController}
tryAgainCallback={() => {
setDownloadedBytes(undefined)
setDownloadErrorMessage(undefined)
}}
isFilePreviewable={isFilePreviewable}
errorDetail={downloadErrorMessage}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ type Props = {
filesController: FilesController
isFilePreviewable: boolean
tryAgainCallback: () => void
errorDetail?: string
}

const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCallback }: Props) => {
const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCallback, errorDetail }: Props) => {
return (
<div className="flex flex-grow flex-col items-center justify-center">
<NoPreviewIllustration className="mb-4 h-30 w-30" />
Expand All @@ -21,8 +22,9 @@ const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCa
{isFilePreviewable ? (
<>
<div className="mb-4 max-w-[35ch] text-center text-sm text-passive-0">
{c('B7.FilesSubscriptionHelp.Files.Error')
.t`There was an error loading the file. Try again, or download the file and open it using another application.`}
{errorDetail ??
c('B7.FilesSubscriptionHelp.Files.Error')
.t`There was an error loading the file. Try again, or download the file and open it using another application.`}
</div>
<div className="flex items-center">
<Button
Expand Down
6 changes: 5 additions & 1 deletion packages/web/src/javascripts/Controllers/FilesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,11 @@ export class FilesController extends AbstractViewController<FilesControllerEvent
})

if (result instanceof ClientDisplayableError) {
throw new Error(result.text)
addToast({
type: ToastType.Error,
message: result.text,
})
return
}

if (isUsingStreamingSaver(saver)) {
Expand Down
Loading