Handle 409/410 when confirming username
This commit is contained in:
parent
10885e5d3f
commit
486ada8b6c
8 changed files with 106 additions and 15 deletions
|
@ -5223,6 +5223,10 @@
|
|||
"message": "Your username couldn’t be saved. Check your connection and try again.",
|
||||
"description": "Shown if something unknown has gone wrong with username save."
|
||||
},
|
||||
"icu:ProfileEditor--username--reservation-gone": {
|
||||
"messageformat": "{username} is no longer available. A new set of digits will be paired with your username, please try saving it again.",
|
||||
"description": "Shown if username reservation has expired and new one needs to be generated."
|
||||
},
|
||||
"ProfileEditor--username--delete-general-error": {
|
||||
"message": "Your username couldn’t be removed. Check your connection and try again.",
|
||||
"description": "Shown if something unknown has gone wrong with username delete."
|
||||
|
|
|
@ -120,7 +120,10 @@ export function EditUsernameModalBody({
|
|||
return i18n('ProfileEditor--username--unavailable');
|
||||
}
|
||||
// Displayed through confirmation modal below
|
||||
if (error === UsernameReservationError.General) {
|
||||
if (
|
||||
error === UsernameReservationError.General ||
|
||||
error === UsernameReservationError.ConflictOrGone
|
||||
) {
|
||||
return;
|
||||
}
|
||||
throw missingCaseError(error);
|
||||
|
@ -264,6 +267,24 @@ export function EditUsernameModalBody({
|
|||
{i18n('ProfileEditor--username--general-error')}
|
||||
</ConfirmationDialog>
|
||||
)}
|
||||
|
||||
{error === UsernameReservationError.ConflictOrGone && (
|
||||
<ConfirmationDialog
|
||||
dialogName="EditUsernameModalBody.conflictOrGone"
|
||||
cancelText={i18n('ok')}
|
||||
cancelButtonVariant={ButtonVariant.Secondary}
|
||||
i18n={i18n}
|
||||
onClose={() => {
|
||||
if (nickname) {
|
||||
reserveUsername(nickname);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n('icu:ProfileEditor--username--reservation-gone', {
|
||||
username: currentUsername,
|
||||
})}
|
||||
</ConfirmationDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { strictAssert } from '../util/assert';
|
|||
import { sleep } from '../util/sleep';
|
||||
import { getMinNickname, getMaxNickname } from '../util/Username';
|
||||
import type { UsernameReservationType } from '../types/Username';
|
||||
import { ReserveUsernameError } from '../types/Username';
|
||||
import { ReserveUsernameError, ConfirmUsernameResult } from '../types/Username';
|
||||
import * as Errors from '../types/errors';
|
||||
import * as log from '../logging/log';
|
||||
import MessageSender from '../textsecure/SendMessage';
|
||||
|
@ -129,7 +129,7 @@ async function updateUsernameAndSyncProfile(
|
|||
export async function confirmUsername(
|
||||
reservation: UsernameReservationType,
|
||||
abortSignal?: AbortSignal
|
||||
): Promise<void> {
|
||||
): Promise<ConfirmUsernameResult> {
|
||||
const { server } = window.textsecure;
|
||||
if (!server) {
|
||||
throw new Error('server interface is not available!');
|
||||
|
@ -162,9 +162,15 @@ export async function confirmUsername(
|
|||
|
||||
return confirmUsername(reservation, abortSignal);
|
||||
}
|
||||
|
||||
if (error.code === 409 || error.code === 410) {
|
||||
return ConfirmUsernameResult.ConflictOrGone;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return ConfirmUsernameResult.Ok;
|
||||
}
|
||||
|
||||
export async function deleteUsername(
|
||||
|
|
|
@ -5,7 +5,10 @@ import type { ThunkAction } from 'redux-thunk';
|
|||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type { UsernameReservationType } from '../../types/Username';
|
||||
import { ReserveUsernameError } from '../../types/Username';
|
||||
import {
|
||||
ReserveUsernameError,
|
||||
ConfirmUsernameResult,
|
||||
} from '../../types/Username';
|
||||
import * as usernameServices from '../../services/username';
|
||||
import type { ReserveUsernameResultType } from '../../services/username';
|
||||
import {
|
||||
|
@ -83,7 +86,7 @@ type ReserveUsernameActionType = ReadonlyDeep<
|
|||
>
|
||||
>;
|
||||
type ConfirmUsernameActionType = ReadonlyDeep<
|
||||
PromiseAction<typeof CONFIRM_USERNAME, void>
|
||||
PromiseAction<typeof CONFIRM_USERNAME, ConfirmUsernameResult>
|
||||
>;
|
||||
type DeleteUsernameActionType = ReadonlyDeep<
|
||||
PromiseAction<typeof DELETE_USERNAME, void>
|
||||
|
@ -425,12 +428,25 @@ export function reducer(
|
|||
'Must be reserving before resolving confirmation'
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
usernameReservation: {
|
||||
state: UsernameReservationState.Closed,
|
||||
},
|
||||
};
|
||||
const { payload } = action;
|
||||
if (payload === ConfirmUsernameResult.Ok) {
|
||||
return {
|
||||
...state,
|
||||
usernameReservation: {
|
||||
state: UsernameReservationState.Closed,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (payload === ConfirmUsernameResult.ConflictOrGone) {
|
||||
return {
|
||||
...state,
|
||||
usernameReservation: {
|
||||
state: UsernameReservationState.Open,
|
||||
error: UsernameReservationError.ConflictOrGone,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw missingCaseError(payload);
|
||||
}
|
||||
|
||||
if (action.type === 'username/CONFIRM_USERNAME_REJECTED') {
|
||||
|
|
|
@ -29,4 +29,5 @@ export enum UsernameReservationError {
|
|||
CheckCharacters = 'CheckCharacters',
|
||||
UsernameNotAvailable = 'UsernameNotAvailable',
|
||||
General = 'General',
|
||||
ConflictOrGone = 'ConflictOrGone',
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@ import { actions } from '../../../state/ducks/username';
|
|||
import { ToastType } from '../../../types/Toast';
|
||||
import { noopAction } from '../../../state/ducks/noop';
|
||||
import { reducer } from '../../../state/reducer';
|
||||
import { ReserveUsernameError } from '../../../types/Username';
|
||||
import {
|
||||
ReserveUsernameError,
|
||||
ConfirmUsernameResult,
|
||||
} from '../../../types/Username';
|
||||
|
||||
const DEFAULT_RESERVATION = {
|
||||
username: 'abc.12',
|
||||
|
@ -312,7 +315,7 @@ describe('electron/state/ducks/username', () => {
|
|||
|
||||
describe('confirmUsername', () => {
|
||||
it('should dispatch promise when reservation is present', () => {
|
||||
const doConfirmUsername = sinon.stub().resolves();
|
||||
const doConfirmUsername = sinon.stub().resolves(ConfirmUsernameResult.Ok);
|
||||
const dispatch = sinon.spy();
|
||||
|
||||
actions.confirmUsername({
|
||||
|
@ -344,7 +347,7 @@ describe('electron/state/ducks/username', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: 'username/CONFIRM_USERNAME_FULFILLED',
|
||||
payload: undefined,
|
||||
payload: ConfirmUsernameResult.Ok,
|
||||
meta: undefined,
|
||||
});
|
||||
|
||||
|
@ -389,6 +392,39 @@ describe('electron/state/ducks/username', () => {
|
|||
UsernameReservationError.General
|
||||
);
|
||||
});
|
||||
|
||||
it('should not close modal on "conflict or gone"', () => {
|
||||
let state = stateWithReservation;
|
||||
|
||||
state = reducer(state, {
|
||||
type: 'username/CONFIRM_USERNAME_PENDING',
|
||||
meta: undefined,
|
||||
});
|
||||
assert.strictEqual(
|
||||
getUsernameReservationState(state),
|
||||
UsernameReservationState.Confirming
|
||||
);
|
||||
assert.strictEqual(
|
||||
getUsernameReservationObject(state),
|
||||
DEFAULT_RESERVATION
|
||||
);
|
||||
|
||||
state = reducer(state, {
|
||||
type: 'username/CONFIRM_USERNAME_FULFILLED',
|
||||
payload: ConfirmUsernameResult.ConflictOrGone,
|
||||
meta: undefined,
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
getUsernameReservationState(state),
|
||||
UsernameReservationState.Open
|
||||
);
|
||||
assert.strictEqual(getUsernameReservationObject(state), undefined);
|
||||
assert.strictEqual(
|
||||
getUsernameReservationError(state),
|
||||
UsernameReservationError.ConflictOrGone
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteUsername', () => {
|
||||
|
|
|
@ -25,7 +25,9 @@ describe('pnp/send gv2 invite', function needsName() {
|
|||
let pniContact: PrimaryDevice;
|
||||
|
||||
beforeEach(async () => {
|
||||
bootstrap = new Bootstrap();
|
||||
bootstrap = new Bootstrap({
|
||||
contactCount: 0,
|
||||
});
|
||||
await bootstrap.init();
|
||||
|
||||
const { phone, server } = bootstrap;
|
||||
|
|
|
@ -12,6 +12,11 @@ export enum ReserveUsernameError {
|
|||
Conflict = 'Conflict',
|
||||
}
|
||||
|
||||
export enum ConfirmUsernameResult {
|
||||
Ok = 'Ok',
|
||||
ConflictOrGone = 'ConflictOrGone',
|
||||
}
|
||||
|
||||
export function getUsernameFromSearch(searchTerm: string): string | undefined {
|
||||
// Search term contains username if it:
|
||||
// - Is a valid username with or without a discriminator
|
||||
|
|
Loading…
Reference in a new issue