From 116dd097bda66196d69d81cf42c8e0bb7f32b141 Mon Sep 17 00:00:00 2001
From: RyotaK <49341894+Ry0taK@users.noreply.github.com>
Date: Wed, 8 Mar 2023 16:32:13 +0900
Subject: [PATCH] =?UTF-8?q?fix:=20=E7=99=BB=E9=8C=B2=E3=83=95=E3=82=A9?=
 =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=81=AB=E3=81=8A=E3=81=91=E3=82=8B=E7=AB=B6?=
 =?UTF-8?q?=E5=90=88=E7=8A=B6=E6=85=8B=E3=82=92=E4=BF=AE=E6=AD=A3=20(#1026?=
 =?UTF-8?q?7)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix: 登録フォームにおける競合状態を修正

* エラーを修正
---
 cypress/e2e/basic.cy.js                       |  4 +++
 packages/frontend/src/components/MkSignup.vue | 26 ++++++++++++++-----
 packages/frontend/src/scripts/api.ts          |  3 ++-
 3 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js
index 5a0a767ed5..8dc07c1800 100644
--- a/cypress/e2e/basic.cy.js
+++ b/cypress/e2e/basic.cy.js
@@ -62,6 +62,10 @@ describe('After setup instance', () => {
 		cy.get('[data-cy-signup-submit]').click();
 
 		cy.wait('@signup');
+  });
+
+  it('signup with duplicated username', () => {
+		cy.registerUser('alice', 'alice1234');
 
 		cy.visitHome();
 
diff --git a/packages/frontend/src/components/MkSignup.vue b/packages/frontend/src/components/MkSignup.vue
index fa69481394..22a8063809 100644
--- a/packages/frontend/src/components/MkSignup.vue
+++ b/packages/frontend/src/components/MkSignup.vue
@@ -110,6 +110,8 @@ let ToSAgreement: boolean = $ref(false);
 let hCaptchaResponse = $ref(null);
 let reCaptchaResponse = $ref(null);
 let turnstileResponse = $ref(null);
+let usernameAbortController: null | AbortController = $ref(null);
+let emailAbortController: null | AbortController = $ref(null);
 
 const shouldDisableSubmitting = $computed((): boolean => {
 	return submitting ||
@@ -141,14 +143,20 @@ function onChangeUsername(): void {
 		}
 	}
 
+	if (usernameAbortController != null) {
+		usernameAbortController.abort();
+	}
 	usernameState = 'wait';
+	usernameAbortController = new AbortController();
 
 	os.api('username/available', {
 		username,
-	}).then(result => {
+	}, undefined, usernameAbortController.signal).then(result => {
 		usernameState = result.available ? 'ok' : 'unavailable';
-	}).catch(() => {
-		usernameState = 'error';
+	}).catch((err) => {
+		if (err.name !== 'AbortError') {
+			usernameState = 'error';
+		}
 	});
 }
 
@@ -158,11 +166,15 @@ function onChangeEmail(): void {
 		return;
 	}
 
+	if (emailAbortController != null) {
+		emailAbortController.abort();
+	}
 	emailState = 'wait';
+	emailAbortController = new AbortController();
 
 	os.api('email-address/available', {
 		emailAddress: email,
-	}).then(result => {
+	}, undefined, emailAbortController.signal).then(result => {
 		emailState = result.available ? 'ok' :
 			result.reason === 'used' ? 'unavailable:used' :
 			result.reason === 'format' ? 'unavailable:format' :
@@ -170,8 +182,10 @@ function onChangeEmail(): void {
 			result.reason === 'mx' ? 'unavailable:mx' :
 			result.reason === 'smtp' ? 'unavailable:smtp' :
 			'unavailable';
-	}).catch(() => {
-		emailState = 'error';
+	}).catch((err) => {
+		if (err.name !== 'AbortError') {
+			emailState = 'error';
+		}
 	});
 }
 
diff --git a/packages/frontend/src/scripts/api.ts b/packages/frontend/src/scripts/api.ts
index 5f34f5333e..97081d170f 100644
--- a/packages/frontend/src/scripts/api.ts
+++ b/packages/frontend/src/scripts/api.ts
@@ -5,7 +5,7 @@ import { $i } from '@/account';
 export const pendingApiRequestsCount = ref(0);
 
 // Implements Misskey.api.ApiClient.request
-export function api<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, data: P = {} as any, token?: string | null | undefined): Promise<Endpoints[E]['res']> {
+export function api<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, data: P = {} as any, token?: string | null | undefined, signal?: AbortSignal): Promise<Endpoints[E]['res']> {
 	pendingApiRequestsCount.value++;
 
 	const onFinally = () => {
@@ -26,6 +26,7 @@ export function api<E extends keyof Endpoints, P extends Endpoints[E]['req']>(en
 			headers: {
 				'Content-Type': 'application/json',
 			},
+			signal,
 		}).then(async (res) => {
 			const body = res.status === 204 ? null : await res.json();