diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7cb8d39f7b0..ff3080191d7 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -5253,7 +5253,7 @@ "description": "Shown in timeline or conversation preview when v2 group changes" }, "icu:GroupV2--admin-approval-bounce--pluralized": { - "messageformat": "{joinerName} requested and cancelled {numberOfRequests, plural, one {their request} other {# requests}} to join via the group link", + "messageformat": "{joinerName} requested and canceled {numberOfRequests, plural, one {their request} other {# requests}} to join via the group link", "description": "Shown in timeline or conversation preview when v2 group changes" }, "icu:GroupV2--group-link-add--disabled--you": { @@ -8902,9 +8902,9 @@ "messageformat": "Thank you for supporting Signal. Your contribution helps fuel the mission of protecting free expression and enabling secure global communication for millions around the world, through open source privacy technology. If you’re a resident of the United States, please retain this receipt for your tax records. Signal Technology Foundation is a tax-exempt nonprofit organization in the United States under section 501c3 of the Internal Revenue Code. Our Federal Tax ID is 82-4506840.", "description": "Footer text shown on donation receipts explaining tax deductibility and Signal's mission" }, - "icu:Donations__Toast__Cancelled": { - "messageformat": "Donation cancelled", - "description": "Toast shown when a donation was manually cancelled by the user" + "icu:Donations__Toast__Canceled": { + "messageformat": "Donation canceled", + "description": "Toast shown when a donation was manually canceled by the user" }, "icu:Donations__Toast__Completed": { "messageformat": "Donation completed", @@ -8978,6 +8978,14 @@ "messageformat": "Your donation is still being processed. This can take a few minutes.", "description": "An explanation for the 'still processing' dialog" }, + "icu:Donations__TimedOut": { + "messageformat": "Donation canceled", + "description": "Title of the dialog shown when the it's been too long since the last step in the donation process" + }, + "icu:Donations__TimedOut__Description": { + "messageformat": "It has been too long since you started your donation. Please start over.", + "description": "An explanation for the 'donation timed out' dialog" + }, "icu:Donations__3dsValidationNeeded": { "messageformat": "Additional verification required", "description": "Title of the dialog shown when the user's payment method requires a redirect to a verification website" diff --git a/app/attachment_channel.ts b/app/attachment_channel.ts index 5b92f6ff2f7..0694eac3dd4 100644 --- a/app/attachment_channel.ts +++ b/app/attachment_channel.ts @@ -159,7 +159,7 @@ async function safeDecryptToSink( await Promise.race([ // Just use a non-existing event name to wait for an 'error'. We want // to handle errors on `sink` while generating digest in case the whole - // request gets cancelled early. + // request gets canceled early. once(sink, 'non-error-event', { signal: controller.signal }), decryptAttachmentV2ToSink(options, sink), ]); diff --git a/app/main.ts b/app/main.ts index 3561f577f98..07ed69d7c7d 100644 --- a/app/main.ts +++ b/app/main.ts @@ -885,7 +885,7 @@ async function createWindow() { ); } if (!shouldClose) { - updater.onRestartCancelled(); + updater.onRestartCanceled(); return; } diff --git a/protos/Backups.proto b/protos/Backups.proto index 5029b24f883..ba7d74938f5 100644 --- a/protos/Backups.proto +++ b/protos/Backups.proto @@ -1189,7 +1189,7 @@ message GroupJoinRequestCanceledUpdate { bytes requestorAci = 1; } -// A single requestor has requested to join and cancelled +// A single requestor has requested to join and canceled // their request repeatedly with no other updates in between. // The last action encompassed by this update is always a // cancellation; if there was another open request immediately @@ -1392,4 +1392,4 @@ message ChatFolder { repeated uint64 includedRecipientIds = 7; // generated recipient id of groups, contacts, and/or note to self repeated uint64 excludedRecipientIds = 8; // generated recipient id of groups, contacts, and/or note to self bytes id = 9; // should be 16 bytes -} \ No newline at end of file +} diff --git a/ts/background.ts b/ts/background.ts index 3a1c933faac..6d089b77de6 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -1517,7 +1517,7 @@ export async function startApp(): Promise { ); if (!window.textsecure.storage.user.getAci()) { log.info( - "Expiration start timestamp cleanup: Cancelling update; we don't have our own UUID" + "Expiration start timestamp cleanup: Canceling update; we don't have our own UUID" ); } else if (messagesUnexpectedlyMissingExpirationStartTimestamp.length) { const newMessageAttributes = diff --git a/ts/components/DonationError.stories.tsx b/ts/components/DonationErrorModal.stories.tsx similarity index 86% rename from ts/components/DonationError.stories.tsx rename to ts/components/DonationErrorModal.stories.tsx index 09102ce3742..f2b8f0f263d 100644 --- a/ts/components/DonationError.stories.tsx +++ b/ts/components/DonationErrorModal.stories.tsx @@ -47,3 +47,12 @@ export function PaymentDeclined(): JSX.Element { /> ); } + +export function TimedOut(): JSX.Element { + return ( + + ); +} diff --git a/ts/components/DonationErrorModal.tsx b/ts/components/DonationErrorModal.tsx index 71ae0a89ffa..624aa0f5a22 100644 --- a/ts/components/DonationErrorModal.tsx +++ b/ts/components/DonationErrorModal.tsx @@ -40,6 +40,11 @@ export function DonationErrorModal(props: PropsType): JSX.Element { body = i18n('icu:Donations__PaymentMethodDeclined__Description'); break; } + case donationErrorTypeSchema.Enum.TimedOut: { + title = i18n('icu:Donations__TimedOut'); + body = i18n('icu:Donations__TimedOut__Description'); + break; + } default: throw missingCaseError(props.errorType); diff --git a/ts/components/DonationVerificationModal.stories.tsx b/ts/components/DonationVerificationModal.stories.tsx index 8baaa987db4..cd9622aa494 100644 --- a/ts/components/DonationVerificationModal.stories.tsx +++ b/ts/components/DonationVerificationModal.stories.tsx @@ -8,6 +8,7 @@ import { action } from '@storybook/addon-actions'; import type { Meta } from '@storybook/react'; import type { PropsType } from './DonationVerificationModal'; import { DonationVerificationModal } from './DonationVerificationModal'; +import { SECOND } from '../util/durations'; const { i18n } = window.SignalContext; @@ -19,8 +20,13 @@ const defaultProps = { i18n, onCancelDonation: action('onCancelDonation'), onOpenBrowser: action('onOpenBrowser'), + onTimedOut: action('onTimedOut'), }; export function Default(): JSX.Element { return ; } + +export function FiveSecondTimeout(): JSX.Element { + return ; +} diff --git a/ts/components/DonationVerificationModal.tsx b/ts/components/DonationVerificationModal.tsx index 16473710c81..f9e8889acd2 100644 --- a/ts/components/DonationVerificationModal.tsx +++ b/ts/components/DonationVerificationModal.tsx @@ -1,20 +1,24 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import type { LocalizerType } from '../types/Util'; import { Modal } from './Modal'; import { Button, ButtonVariant } from './Button'; +import { DAY } from '../util/durations'; export type PropsType = { + // Test-only + _timeout?: number; i18n: LocalizerType; onCancelDonation: () => unknown; onOpenBrowser: () => unknown; + onTimedOut: () => unknown; }; export function DonationVerificationModal(props: PropsType): JSX.Element { - const { i18n, onCancelDonation, onOpenBrowser } = props; + const { _timeout, i18n, onCancelDonation, onOpenBrowser, onTimedOut } = props; const [hasOpenedBrowser, setHasOpenedBrowser] = useState(false); const titleText = hasOpenedBrowser @@ -40,6 +44,19 @@ export function DonationVerificationModal(props: PropsType): JSX.Element { ); + useEffect(() => { + let timeout: NodeJS.Timeout | undefined = setTimeout(() => { + timeout = undefined; + onTimedOut(); + }, _timeout ?? DAY); + + return () => { + if (timeout) { + clearTimeout(timeout); + } + }; + }, [_timeout, onTimedOut]); + return ( { clearWorkflow(); setPage(SettingsPage.Donations); - showToast({ toastType: ToastType.DonationCancelled }); + showToast({ toastType: ToastType.DonationCanceled }); }} onRetryDonation={() => { resumeWorkflow(); @@ -558,11 +561,16 @@ export function PreferencesDonations({ onCancelDonation={() => { clearWorkflow(); setPage(SettingsPage.Donations); - showToast({ toastType: ToastType.DonationCancelled }); + showToast({ toastType: ToastType.DonationCanceled }); }} onOpenBrowser={() => { openLinkInWebBrowser(workflow.redirectTarget); }} + onTimedOut={() => { + clearWorkflow(); + updateLastError(donationErrorTypeSchema.Enum.TimedOut); + setPage(SettingsPage.Donations); + }} /> ); } else if ( diff --git a/ts/components/ToastManager.stories.tsx b/ts/components/ToastManager.stories.tsx index 1f948e83760..814eb189258 100644 --- a/ts/components/ToastManager.stories.tsx +++ b/ts/components/ToastManager.stories.tsx @@ -100,8 +100,10 @@ function getToast(toastType: ToastType): AnyToast { }; case ToastType.DeleteForEveryoneFailed: return { toastType: ToastType.DeleteForEveryoneFailed }; - case ToastType.DonationCancelled: - return { toastType: ToastType.DonationCancelled }; + case ToastType.DonationCanceled: + return { toastType: ToastType.DonationCanceled }; + case ToastType.DonationCanceledWithView: + return { toastType: ToastType.DonationCanceledWithView }; case ToastType.DonationCompleted: return { toastType: ToastType.DonationCompleted }; case ToastType.DonationConfirmationNeeded: diff --git a/ts/components/ToastManager.tsx b/ts/components/ToastManager.tsx index 1dd66e96759..62731d1bd6f 100644 --- a/ts/components/ToastManager.tsx +++ b/ts/components/ToastManager.tsx @@ -280,10 +280,10 @@ export function renderToast({ ); } - if (toastType === ToastType.DonationCancelled) { + if (toastType === ToastType.DonationCanceled) { return ( - {i18n('icu:Donations__Toast__Cancelled')} + {i18n('icu:Donations__Toast__Canceled')} ); } @@ -337,12 +337,16 @@ export function renderToast({ } if ( + toastType === ToastType.DonationCanceledWithView || toastType === ToastType.DonationConfirmationNeeded || toastType === ToastType.DonationError || toastType === ToastType.DonationVerificationFailed || toastType === ToastType.DonationVerificationNeeded ) { const mapping = { + [ToastType.DonationCanceledWithView]: i18n( + 'icu:Donations__Toast__Canceled' + ), [ToastType.DonationConfirmationNeeded]: i18n( 'icu:Donations__Toast__ConfirmationNeeded' ), diff --git a/ts/jobs/AttachmentDownloadManager.ts b/ts/jobs/AttachmentDownloadManager.ts index 3f8c4d9e8c3..9ec03dc1dae 100644 --- a/ts/jobs/AttachmentDownloadManager.ts +++ b/ts/jobs/AttachmentDownloadManager.ts @@ -478,7 +478,7 @@ async function runDownloadAttachmentJob({ } catch (error) { if (options.abortSignal.aborted) { log.info( - `${logId}: Cancelled attempt ${job.attempts}. Not scheduling a retry.` + `${logId}: Canceled attempt ${job.attempts}. Not scheduling a retry. Error:` ); // Remove `pending` flag from the attachment. User can retry later. await addAttachmentToMessage( diff --git a/ts/jobs/JobManager.ts b/ts/jobs/JobManager.ts index d457d4039d9..a0ba176b6a8 100644 --- a/ts/jobs/JobManager.ts +++ b/ts/jobs/JobManager.ts @@ -414,7 +414,7 @@ export abstract class JobManager { abortController.abort(); // First tell those waiting for the job that it's not happening - const rejectionError = new Error('Cancelled at JobManager.cancelJobs'); + const rejectionError = new Error('Canceled at JobManager.cancelJobs'); const idWithAttempts = this.#getJobIdIncludingAttempts(job); this.#jobCompletePromises.get(idWithAttempts)?.reject(rejectionError); this.#jobCompletePromises.delete(idWithAttempts); @@ -438,7 +438,7 @@ export abstract class JobManager { }) ); - log.warn(`${logId}: Successfully cancelled ${jobs.length} jobs`); + log.warn(`${logId}: Successfully canceled ${jobs.length} jobs`); } #addRunningJob( diff --git a/ts/jobs/conversationJobQueue.ts b/ts/jobs/conversationJobQueue.ts index e3a7dbc58a8..6a5f8e48259 100644 --- a/ts/jobs/conversationJobQueue.ts +++ b/ts/jobs/conversationJobQueue.ts @@ -868,7 +868,7 @@ export class ConversationJobQueue extends JobQueue { ) { if (type === conversationQueueJobEnum.enum.ProfileKey) { log.warn( - "Cancelling profile share, we don't want to wait for pending verification." + "Canceling profile share, we don't want to wait for pending verification." ); return undefined; } @@ -895,18 +895,16 @@ export class ConversationJobQueue extends JobQueue { if ( verificationData.type === - ConversationVerificationState.VerificationCancelled + ConversationVerificationState.VerificationCanceled ) { if (verificationData.canceledAt >= timestamp) { - log.info( - 'cancelling job; user cancelled out of verification dialog.' - ); + log.info('canceling job; user canceled out of verification dialog.'); shouldContinue = false; } else { log.info( 'clearing cancellation tombstone; continuing ahead with job' ); - window.reduxActions.conversations.clearCancelledConversationVerification( + window.reduxActions.conversations.clearCanceledConversationVerification( conversation.id ); } @@ -984,7 +982,7 @@ export class ConversationJobQueue extends JobQueue { // Note: This should never happen, because the zod call in parseData wouldn't // accept data that doesn't look like our type specification. const problem: never = type; - log.error(`Got job with type ${problem}; Cancelling job.`); + log.error(`Got job with type ${problem}; Canceling job.`); } } @@ -1056,14 +1054,14 @@ export class ConversationJobQueue extends JobQueue { if (untrustedServiceIds.length) { if (type === jobSet.ProfileKey || type === jobSet.ProfileKeyForCall) { log.warn( - `Cancelling profile share, since there were ${untrustedServiceIds.length} untrusted send targets.` + `Canceling profile share, since there were ${untrustedServiceIds.length} untrusted send targets.` ); return undefined; } if (type === jobSet.Receipts) { log.warn( - `Cancelling receipt send, since there were ${untrustedServiceIds.length} untrusted send targets.` + `Canceling receipt send, since there were ${untrustedServiceIds.length} untrusted send targets.` ); return undefined; } diff --git a/ts/jobs/helpers/sendCallingMessage.ts b/ts/jobs/helpers/sendCallingMessage.ts index 75a000ac118..b8e640446c1 100644 --- a/ts/jobs/helpers/sendCallingMessage.ts +++ b/ts/jobs/helpers/sendCallingMessage.ts @@ -134,7 +134,7 @@ export async function sendCallingMessage( error instanceof UnregisteredUserError ) { log.info( - `${logId}: Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Cancelling job.` + `${logId}: Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Canceling job.` ); return; } diff --git a/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts b/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts index d1c471456b4..806847e92d6 100644 --- a/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts +++ b/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts @@ -40,7 +40,7 @@ export async function sendDirectExpirationTimerUpdate( if (!isDirectConversation(conversation.attributes)) { log.error( - `Conversation ${conversation.idForLogging()} is not a 1:1 conversation; cancelling expiration timer job.` + `Conversation ${conversation.idForLogging()} is not a 1:1 conversation; canceling expiration timer job.` ); return; } @@ -91,7 +91,7 @@ export async function sendDirectExpirationTimerUpdate( if (!proto.dataMessage) { log.error( - "ContentMessage proto didn't have a data message; cancelling job." + "ContentMessage proto didn't have a data message; canceling job." ); return; } diff --git a/ts/jobs/helpers/sendNullMessage.ts b/ts/jobs/helpers/sendNullMessage.ts index 2c1b3f0cbcb..657f412e336 100644 --- a/ts/jobs/helpers/sendNullMessage.ts +++ b/ts/jobs/helpers/sendNullMessage.ts @@ -142,7 +142,7 @@ export async function sendNullMessage( error instanceof UnregisteredUserError ) { log.info( - 'Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Cancelling job.' + 'Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Canceling job.' ); return; } diff --git a/ts/jobs/helpers/sendProfileKey.ts b/ts/jobs/helpers/sendProfileKey.ts index 2464e1381b1..71025920e87 100644 --- a/ts/jobs/helpers/sendProfileKey.ts +++ b/ts/jobs/helpers/sendProfileKey.ts @@ -79,13 +79,13 @@ export async function sendProfileKey( } if (!data?.isOneTimeSend && !conversation.get('profileSharing')) { - log.info('No longer sharing profile. Cancelling job.'); + log.info('No longer sharing profile. Canceling job.'); return; } const profileKey = await ourProfileKeyService.get(); if (!profileKey) { - log.info('Unable to fetch profile. Cancelling job.'); + log.info('Unable to fetch profile. Canceling job.'); return; } diff --git a/ts/jobs/helpers/sendResendRequest.ts b/ts/jobs/helpers/sendResendRequest.ts index eb0ae39777d..a53d4bf2d8d 100644 --- a/ts/jobs/helpers/sendResendRequest.ts +++ b/ts/jobs/helpers/sendResendRequest.ts @@ -76,12 +76,12 @@ export async function sendResendRequest( ); if (!isDirectConversation(conversation.attributes)) { - log.error('conversation is not direct, cancelling job.'); + log.error('conversation is not direct, canceling job.'); return; } if (isConversationUnregistered(conversation.attributes)) { - log.error('conversation is unregistered, cancelling job.'); + log.error('conversation is unregistered, canceling job.'); failoverToLocalReset(log, data); return; } @@ -90,7 +90,7 @@ export async function sendResendRequest( // Any needed blocking should still apply once the decryption error is fixed. if (conversation.getAci() !== senderAci) { - log.error('conversation was missing a aci, cancelling job.'); + log.error('conversation was missing a aci, canceling job.'); failoverToLocalReset(log, data); return; } @@ -171,7 +171,7 @@ export async function sendResendRequest( error instanceof UnregisteredUserError ) { log.info( - 'Group send failures were all OutgoingIdentityKeyError or UnregisteredUserError. Cancelling job.' + 'Group send failures were all OutgoingIdentityKeyError or UnregisteredUserError. Canceling job.' ); return; diff --git a/ts/jobs/helpers/sendSavedProto.ts b/ts/jobs/helpers/sendSavedProto.ts index 55043549ace..c7e01699f2a 100644 --- a/ts/jobs/helpers/sendSavedProto.ts +++ b/ts/jobs/helpers/sendSavedProto.ts @@ -61,7 +61,7 @@ export async function sendSavedProto( const serviceId = conversation.getServiceId(); if (!serviceId) { log.info( - `conversation ${conversation.idForLogging()} was missing serviceId, cancelling job.` + `conversation ${conversation.idForLogging()} was missing serviceId, canceling job.` ); return; } @@ -101,7 +101,7 @@ export async function sendSavedProto( error instanceof UnregisteredUserError ) { log.info( - 'Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Cancelling job.' + 'Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Canceling job.' ); return; } diff --git a/ts/jobs/helpers/sendSenderKeyDistribution.ts b/ts/jobs/helpers/sendSenderKeyDistribution.ts index 5aecdcfd990..577b4336124 100644 --- a/ts/jobs/helpers/sendSenderKeyDistribution.ts +++ b/ts/jobs/helpers/sendSenderKeyDistribution.ts @@ -73,14 +73,14 @@ export async function sendSenderKeyDistribution( if (!distributionId) { log.info( - `group ${group?.idForLogging()} had no distributionid, cancelling job.` + `group ${group?.idForLogging()} had no distributionid, canceling job.` ); return; } if (!serviceId) { log.info( - `conversation ${conversation.idForLogging()} was missing serviceId, cancelling job.` + `conversation ${conversation.idForLogging()} was missing serviceId, canceling job.` ); return; } @@ -106,7 +106,7 @@ export async function sendSenderKeyDistribution( error instanceof UnregisteredUserError ) { log.info( - 'Send failure was NoSenderKeyError, OutgoingIdentityKeyError or UnregisteredUserError. Cancelling job.' + 'Send failure was NoSenderKeyError, OutgoingIdentityKeyError or UnregisteredUserError. Canceling job.' ); return; } diff --git a/ts/services/donations.ts b/ts/services/donations.ts index 9d48d3ed204..be2e049c80b 100644 --- a/ts/services/donations.ts +++ b/ts/services/donations.ts @@ -87,6 +87,21 @@ export async function initialize(): Promise { const shouldShowToast = didResumeWorkflowAtStartup() && !isDonationPageVisible(); + const isTooOld = isOlderThan(workflow.timestamp, DAY); + + if ( + isTooOld && + (workflow.type === donationStateSchema.Enum.INTENT_METHOD || + workflow.type === donationStateSchema.Enum.INTENT_REDIRECT) + ) { + log.info( + `initialize: Workflow at ${workflow.type} is too old, canceling donation.` + ); + await clearDonation(); + await failDonation(donationErrorTypeSchema.Enum.TimedOut); + + return; + } if (workflow.type === donationStateSchema.Enum.INTENT_METHOD) { if (shouldShowToast) { @@ -783,6 +798,13 @@ async function failDonation(errorType: DonationErrorType): Promise { window.reduxActions.toast.showToast({ toastType: ToastType.DonationVerificationFailed, }); + } else if (errorType === donationErrorTypeSchema.Enum.TimedOut) { + log.info( + `${logId}: Donation page not visible. Showing 'donation canceled w/view' toast.` + ); + window.reduxActions.toast.showToast({ + toastType: ToastType.DonationCanceledWithView, + }); } else { log.info( `${logId}: Donation page not visible. Showing 'error processing donation' toast.` @@ -891,7 +913,8 @@ function isDonationPageVisible() { const { selectedLocation } = window.reduxStore.getState().nav; return ( selectedLocation.tab === NavTab.Settings && - (selectedLocation.details.page === SettingsPage.DonationsDonateFlow || + (selectedLocation.details.page === SettingsPage.Donations || + selectedLocation.details.page === SettingsPage.DonationsDonateFlow || selectedLocation.details.page === SettingsPage.DonationsReceiptList) ); } diff --git a/ts/services/profiles.ts b/ts/services/profiles.ts index 25ec4b8978b..68279093e68 100644 --- a/ts/services/profiles.ts +++ b/ts/services/profiles.ts @@ -200,9 +200,7 @@ export class ProfileService { this.#jobsByConversationId.forEach(job => { job.reject( - new Error( - `ProfileService.clearAll: job cancelled because '${reason}'` - ) + new Error(`ProfileService.clearAll: job canceled because '${reason}'`) ); }); diff --git a/ts/services/storageRecordOps.ts b/ts/services/storageRecordOps.ts index e5a945682d5..a685e9ff681 100644 --- a/ts/services/storageRecordOps.ts +++ b/ts/services/storageRecordOps.ts @@ -483,12 +483,12 @@ export function toAccountRecord( if (typeof subscriberCurrencyCode === 'string') { accountRecord.donorSubscriberCurrencyCode = subscriberCurrencyCode; } - const donorSubscriptionManuallyCancelled = window.storage.get( + const donorSubscriptionManuallyCanceled = window.storage.get( 'donorSubscriptionManuallyCancelled' ); - if (typeof donorSubscriptionManuallyCancelled === 'boolean') { + if (typeof donorSubscriptionManuallyCanceled === 'boolean') { accountRecord.donorSubscriptionManuallyCancelled = - donorSubscriptionManuallyCancelled; + donorSubscriptionManuallyCanceled; } accountRecord.backupSubscriberData = generateBackupsSubscriberData(); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 0564310af4f..9e0f67af474 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -506,7 +506,7 @@ export type ConversationVerificationData = ReadonlyDeep< >; } | { - type: ConversationVerificationState.VerificationCancelled; + type: ConversationVerificationState.VerificationCanceled; canceledAt: number; } >; @@ -618,8 +618,7 @@ export const getConversationCallMode = ( const CANCEL_CONVERSATION_PENDING_VERIFICATION = 'conversations/CANCEL_CONVERSATION_PENDING_VERIFICATION'; -const CLEAR_CANCELLED_VERIFICATION = - 'conversations/CLEAR_CANCELLED_VERIFICATION'; +const CLEAR_CANCELED_VERIFICATION = 'conversations/CLEAR_CANCELED_VERIFICATION'; const CLEAR_CONVERSATIONS_PENDING_VERIFICATION = 'conversations/CLEAR_CONVERSATIONS_PENDING_VERIFICATION'; export const COLORS_CHANGED = 'conversations/COLORS_CHANGED'; @@ -668,8 +667,8 @@ type ClearInvitedServiceIdsForNewlyCreatedGroupActionType = ReadonlyDeep<{ type ClearVerificationDataByConversationActionType = ReadonlyDeep<{ type: typeof CLEAR_CONVERSATIONS_PENDING_VERIFICATION; }>; -type ClearCancelledVerificationActionType = ReadonlyDeep<{ - type: typeof CLEAR_CANCELLED_VERIFICATION; +type ClearCanceledVerificationActionType = ReadonlyDeep<{ + type: typeof CLEAR_CANCELED_VERIFICATION; payload: { conversationId: string; }; @@ -1048,7 +1047,7 @@ export type ConsumePreloadDataActionType = ReadonlyDeep<{ export type ConversationActionType = | AddPreloadDataActionType | CancelVerificationDataByConversationActionType - | ClearCancelledVerificationActionType + | ClearCanceledVerificationActionType | ClearGroupCreationErrorActionType | ClearInvitedServiceIdsForNewlyCreatedGroupActionType | ClearTargetedMessageActionType @@ -1134,7 +1133,7 @@ export const actions = { cancelAttachmentDownload, cancelConversationVerification, changeHasGroupLink, - clearCancelledConversationVerification, + clearCanceledConversationVerification, clearGroupCreationError, clearInvitedServiceIdsForNewlyCreatedGroup, clearTargetedMessage, @@ -2783,11 +2782,11 @@ function verifyConversationsStoppingSend(): ThunkAction< }; } -export function clearCancelledConversationVerification( +export function clearCanceledConversationVerification( conversationId: string -): ClearCancelledVerificationActionType { +): ClearCanceledVerificationActionType { return { - type: CLEAR_CANCELLED_VERIFICATION, + type: CLEAR_CANCELED_VERIFICATION, payload: { conversationId, }, @@ -5229,7 +5228,7 @@ function getVerificationDataForConversation({ if ( !existing || - existing.type === ConversationVerificationState.VerificationCancelled + existing.type === ConversationVerificationState.VerificationCanceled ) { return { [conversationId]: { @@ -5527,7 +5526,7 @@ export function reducer( }; } - if (action.type === CLEAR_CANCELLED_VERIFICATION) { + if (action.type === CLEAR_CANCELED_VERIFICATION) { const { conversationId } = action.payload; const { verificationDataByConversation } = state; @@ -5568,13 +5567,13 @@ export function reducer( for (const [conversationId, data] of entries) { if ( - data.type === ConversationVerificationState.VerificationCancelled && + data.type === ConversationVerificationState.VerificationCanceled && data.canceledAt > canceledAt ) { newverificationDataByConversation[conversationId] = data; } else { newverificationDataByConversation[conversationId] = { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt, }; } diff --git a/ts/state/ducks/conversationsEnums.ts b/ts/state/ducks/conversationsEnums.ts index e783e215de5..ace095e4e56 100644 --- a/ts/state/ducks/conversationsEnums.ts +++ b/ts/state/ducks/conversationsEnums.ts @@ -23,7 +23,7 @@ export enum OneTimeModalState { export enum ConversationVerificationState { PendingVerification = 'PendingVerification', - VerificationCancelled = 'VerificationCancelled', + VerificationCanceled = 'VerificationCanceled', } export enum TargetedMessageSource { diff --git a/ts/state/ducks/donations.ts b/ts/state/ducks/donations.ts index fab7ad0397e..43543dd18df 100644 --- a/ts/state/ducks/donations.ts +++ b/ts/state/ducks/donations.ts @@ -261,8 +261,15 @@ export function reducer( if (action.type === UPDATE_WORKFLOW) { const { nextWorkflow } = action.payload; + // If we've cleared the workflow or are starting afresh, we clear the startup flag + const didResumeWorkflowAtStartup = + !nextWorkflow || nextWorkflow.type === donationStateSchema.Enum.INTENT + ? false + : state.didResumeWorkflowAtStartup; + return { ...state, + didResumeWorkflowAtStartup, currentWorkflow: nextWorkflow, }; } diff --git a/ts/state/ducks/nav.ts b/ts/state/ducks/nav.ts index 4b0838134de..79c5896004d 100644 --- a/ts/state/ducks/nav.ts +++ b/ts/state/ducks/nav.ts @@ -61,7 +61,7 @@ export function changeLocation( }); if (needToCancel) { - log.info(`${logId}: Cancelling navigation`); + log.info(`${logId}: Canceling navigation`); return; } diff --git a/ts/test-electron/services/profiles_test.ts b/ts/test-electron/services/profiles_test.ts index c30f4596e67..4f2ab242937 100644 --- a/ts/test-electron/services/profiles_test.ts +++ b/ts/test-electron/services/profiles_test.ts @@ -54,10 +54,10 @@ describe('util/profiles', () => { service.clearAll('testing'); - await assert.isRejected(promise1, 'job cancelled'); - await assert.isRejected(promise2, 'job cancelled'); - await assert.isRejected(promise3, 'job cancelled'); - await assert.isRejected(promise4, 'job cancelled'); + await assert.isRejected(promise1, 'job canceled'); + await assert.isRejected(promise2, 'job canceled'); + await assert.isRejected(promise3, 'job canceled'); + await assert.isRejected(promise4, 'job canceled'); }); }); @@ -147,9 +147,9 @@ describe('util/profiles', () => { // Never queued const promise5 = service.get(SERVICE_ID_5, null); - await assert.isRejected(promise2, 'job cancelled'); - await assert.isRejected(promise3, 'job cancelled'); - await assert.isRejected(promise4, 'job cancelled'); + await assert.isRejected(promise2, 'job canceled'); + await assert.isRejected(promise3, 'job canceled'); + await assert.isRejected(promise4, 'job canceled'); await assert.isRejected(promise5, 'paused queue'); assert.strictEqual(runCount, 3, 'after await'); @@ -184,9 +184,9 @@ describe('util/profiles', () => { // Queued, because we aren't pausing const promise5 = service.get(SERVICE_ID_5, null); - await assert.isRejected(promise2, 'job cancelled'); - await assert.isRejected(promise3, 'job cancelled'); - await assert.isRejected(promise4, 'job cancelled'); + await assert.isRejected(promise2, 'job canceled'); + await assert.isRejected(promise3, 'job canceled'); + await assert.isRejected(promise4, 'job canceled'); // It didn't succeed, but we log and resolve as normal await assert.isFulfilled(promise5); diff --git a/ts/test-electron/state/ducks/conversations_test.ts b/ts/test-electron/state/ducks/conversations_test.ts index 1b872cad089..bc4161809dc 100644 --- a/ts/test-electron/state/ducks/conversations_test.ts +++ b/ts/test-electron/state/ducks/conversations_test.ts @@ -29,7 +29,7 @@ import { TARGETED_CONVERSATION_CHANGED, actions, cancelConversationVerification, - clearCancelledConversationVerification, + clearCanceledConversationVerification, getConversationCallMode, getEmptyState, reducer, @@ -940,12 +940,12 @@ describe('both/state/ducks/conversations', () => { }); }); - it('stomps on VerificationCancelled state', () => { + it('stomps on VerificationCanceled state', () => { const state: ConversationsStateType = { ...getEmptyState(), verificationDataByConversation: { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: Date.now(), }, }, @@ -1111,19 +1111,19 @@ describe('both/state/ducks/conversations', () => { assert.deepStrictEqual(actual.verificationDataByConversation, { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: now, }, }); }); - it('updates timestamp for existing VerificationCancelled state', () => { + it('updates timestamp for existing VerificationCanceled state', () => { const now = Date.now(); const state: ConversationsStateType = { ...getEmptyState(), verificationDataByConversation: { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: now - 1, }, }, @@ -1133,19 +1133,19 @@ describe('both/state/ducks/conversations', () => { assert.deepStrictEqual(actual.verificationDataByConversation, { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: now, }, }); }); - it('uses newest timestamp when updating existing VerificationCancelled state', () => { + it('uses newest timestamp when updating existing VerificationCanceled state', () => { const now = Date.now(); const state: ConversationsStateType = { ...getEmptyState(), verificationDataByConversation: { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: now, }, }, @@ -1155,7 +1155,7 @@ describe('both/state/ducks/conversations', () => { assert.deepStrictEqual(actual.verificationDataByConversation, { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: now, }, }); @@ -1171,20 +1171,20 @@ describe('both/state/ducks/conversations', () => { }); describe('CANCEL_CONVERSATION_PENDING_VERIFICATION', () => { - it('removes existing VerificationCancelled state', () => { + it('removes existing VerificationCanceled state', () => { const now = Date.now(); const state: ConversationsStateType = { ...getEmptyState(), verificationDataByConversation: { 'convo A': { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: now, }, }, }; const actual = reducer( state, - clearCancelledConversationVerification('convo A') + clearCanceledConversationVerification('convo A') ); assert.deepStrictEqual(actual.verificationDataByConversation, {}); @@ -1202,7 +1202,7 @@ describe('both/state/ducks/conversations', () => { }; const actual = reducer( state, - clearCancelledConversationVerification('convo A') + clearCanceledConversationVerification('convo A') ); assert.deepStrictEqual(actual, state); @@ -1212,7 +1212,7 @@ describe('both/state/ducks/conversations', () => { const state: ConversationsStateType = getEmptyState(); const actual = reducer( state, - clearCancelledConversationVerification('convo A') + clearCanceledConversationVerification('convo A') ); assert.deepStrictEqual(actual, state); diff --git a/ts/test-electron/state/selectors/conversations-extra_test.ts b/ts/test-electron/state/selectors/conversations-extra_test.ts index 112370f53b7..51ad4957e63 100644 --- a/ts/test-electron/state/selectors/conversations-extra_test.ts +++ b/ts/test-electron/state/selectors/conversations-extra_test.ts @@ -234,11 +234,11 @@ describe('both/state/selectors/conversations-extra', () => { ...state.conversations, verificationDataByConversation: { direct1: { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: Date.now(), }, direct2: { - type: ConversationVerificationState.VerificationCancelled, + type: ConversationVerificationState.VerificationCanceled, canceledAt: Date.now(), }, }, diff --git a/ts/test-mock/backups/backups_test.ts b/ts/test-mock/backups/backups_test.ts index 7dc756d61cc..01f81a206a3 100644 --- a/ts/test-mock/backups/backups_test.ts +++ b/ts/test-mock/backups/backups_test.ts @@ -424,7 +424,7 @@ describe('backups', function (this: Mocha.Suite) { await window.locator('.module-message >> "Message 33"').waitFor(); }); - it('handles remote ephemeral backup cancelation', async function () { + it('handles remote ephemeral backup cancellation', async function () { const ephemeralBackupKey = randomBytes(32); const { phone, server } = bootstrap; diff --git a/ts/textsecure/SocketManager.ts b/ts/textsecure/SocketManager.ts index d7460454527..fb2fac0a34a 100644 --- a/ts/textsecure/SocketManager.ts +++ b/ts/textsecure/SocketManager.ts @@ -282,7 +282,7 @@ export class SocketManager extends EventListener { try { await sleep(timeout, reconnectController.signal); } catch { - log.info('reconnect cancelled'); + log.info('reconnect canceled'); return; } finally { if (this.#reconnectController === reconnectController) { diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index e22bdf8653a..ebc2ed7bebf 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -2108,10 +2108,10 @@ export function initialize({ } function cancelInflightRequests(reason: string) { const logId = `cancelInflightRequests/${reason}`; - log.warn(`${logId}: Cancelling ${inflightRequests.size} requests`); + log.warn(`${logId}: Canceling ${inflightRequests.size} requests`); for (const request of inflightRequests) { try { - request(new Error(`${logId}: Cancelled!`)); + request(new Error(`${logId}: Canceled!`)); } catch (error: unknown) { log.error( `${logId}: Failed to cancel request: ${toLogFormat(error)}` diff --git a/ts/types/Donations.ts b/ts/types/Donations.ts index 8a90c393375..de0208836f2 100644 --- a/ts/types/Donations.ts +++ b/ts/types/Donations.ts @@ -21,6 +21,8 @@ export const donationErrorTypeSchema = z.enum([ 'GeneralError', // Any 4xx error when adding payment method or confirming intent 'PaymentDeclined', + // When it's been too long since the last step of the donation, and card wasn't charged + 'TimedOut', ]); export type DonationErrorType = z.infer; diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index fda90dfaf53..c652711f1d9 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -180,6 +180,7 @@ export type StorageAccessType = { areWeASubscriber: boolean; subscriberId: Uint8Array; subscriberCurrencyCode: string; + // Note: for historical reasons, this has two l's donorSubscriptionManuallyCancelled: boolean; backupsSubscriberId: Uint8Array; backupsSubscriberPurchaseToken: string; diff --git a/ts/types/Toast.tsx b/ts/types/Toast.tsx index 66809a3f411..02fe916fd52 100644 --- a/ts/types/Toast.tsx +++ b/ts/types/Toast.tsx @@ -31,7 +31,8 @@ export enum ToastType { DecryptionError = 'DecryptionError', DebugLogError = 'DebugLogError', DeleteForEveryoneFailed = 'DeleteForEveryoneFailed', - DonationCancelled = 'DonationCancelled', + DonationCanceled = 'DonationCanceled', + DonationCanceledWithView = 'DonationCanceledWithView', DonationCompleted = 'DonationCompleted', DonationConfirmationNeeded = 'DonationConfirmationNeeded', DonationError = 'DonationError', @@ -127,7 +128,8 @@ export type AnyToast = | { toastType: ToastType.DangerousFileType } | { toastType: ToastType.DebugLogError } | { toastType: ToastType.DeleteForEveryoneFailed } - | { toastType: ToastType.DonationCancelled } + | { toastType: ToastType.DonationCanceled } + | { toastType: ToastType.DonationCanceledWithView } | { toastType: ToastType.DonationCompleted } | { toastType: ToastType.DonationConfirmationNeeded } | { toastType: ToastType.DonationError } diff --git a/ts/updater/common.ts b/ts/updater/common.ts index e6031b1faa6..fe5d392f115 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -192,15 +192,15 @@ export abstract class Updater { return this.#checkForUpdatesMaybeInstall(CheckType.ForceDownload); } - // If the updater was about to restart the app but the user cancelled it, show dialog + // If the updater was about to restart the app but the user canceled it, show dialog // to let them retry the restart - public onRestartCancelled(): void { + public onRestartCanceled(): void { if (!this.#restarting) { return; } this.logger.info( - 'updater/onRestartCancelled: restart was cancelled. forcing update to reset updater state' + 'updater/onRestartCanceled: restart was canceled. forcing update to reset updater state' ); this.#restarting = false; markShouldNotQuit(); diff --git a/ts/updater/index.ts b/ts/updater/index.ts index 950a9de5d63..e9484793afb 100644 --- a/ts/updater/index.ts +++ b/ts/updater/index.ts @@ -56,9 +56,9 @@ export async function force(): Promise { } } -export function onRestartCancelled(): void { +export function onRestartCanceled(): void { if (updater) { - updater.onRestartCancelled(); + updater.onRestartCanceled(); } } diff --git a/ts/util/createIPCEvents.ts b/ts/util/createIPCEvents.ts index 72dc1abc680..21e26970a66 100644 --- a/ts/util/createIPCEvents.ts +++ b/ts/util/createIPCEvents.ts @@ -291,7 +291,7 @@ export function createIPCEvents( ); return true; } catch { - log.info('requestCloseConfirmation: Close cancelled by user.'); + log.info('requestCloseConfirmation: Close canceled by user.'); return false; } },