From 69f31e624699d92f563e89cedf135afe5edc8e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:00:49 +0900 Subject: [PATCH] =?UTF-8?q?enhance(frontend):=20=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=82=82=E8=A1=A8=E7=A4=BA=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(MisskeyIO#738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +登録時のAPIエラーを表示できるように --- .../src/components/MkSignupDialog.form.vue | 40 +++---- packages/frontend/src/os.ts | 111 ++++++++++-------- packages/frontend/src/scripts/misskey-api.ts | 9 +- 3 files changed, 85 insertions(+), 75 deletions(-) diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index 52d910afc69d..4c923de37543 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -189,7 +189,7 @@ async function onSubmit(): Promise { submitting.value = true; try { - await misskeyApi('signup', { + await os.apiWithDialog('signup', { username: username.value, password: password.value.password, emailAddress: email.value, @@ -198,35 +198,27 @@ async function onSubmit(): Promise { 'm-captcha-response': mCaptchaResponse.value, 'g-recaptcha-response': reCaptchaResponse.value, 'turnstile-response': turnstileResponse.value, - }); - if (instance.emailRequiredForSignup) { - os.alert({ - type: 'success', - title: i18n.ts._signup.almostThere, - text: i18n.tsx._signup.emailSent({ email: email.value }), - }); - emit('signupEmailPending'); - } else { - const res = await misskeyApi('signin', { - username: username.value, - password: password.value.password, - }); - emit('signup', res); - - if (props.autoSet) { - return login(res.i); + }, undefined, (res) => { + if (instance.emailRequiredForSignup) { + os.alert({ + type: 'success', + title: i18n.ts._signup.almostThere, + text: i18n.tsx._signup.emailSent({ email: email.value }), + }); + emit('signupEmailPending'); + } else { + emit('signup', { id: res.id, i: res.token }); + + if (props.autoSet) { + login(res.token); + } } - } + }); } catch { submitting.value = false; hcaptcha.value?.reset?.(); recaptcha.value?.reset?.(); turnstile.value?.reset?.(); - - os.alert({ - type: 'error', - text: i18n.ts.somethingHappened, - }); } } diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index b4e8bf3f8125..5560335d838e 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -34,60 +34,77 @@ export const apiWithDialog = ( { + onSuccess?: ((res: Misskey.api.SwitchCaseResponseType) => void) | null | undefined, + onFailure?: ((err: Misskey.api.APIError) => void) | null, +): Promise> => { const promise = misskeyApi(endpoint, data, token); - promiseDialog(promise, null, async (err) => { - let title: string | undefined; - let text = err.message + '\n' + err.id; - if (err.code === 'INTERNAL_ERROR') { - title = i18n.ts.internalServerError; - text = i18n.ts.internalServerErrorDescription; - const date = new Date().toISOString(); - const { result } = await actions({ - type: 'error', - title, - text, - details: err.info, - actions: [{ - value: 'ok', - text: i18n.ts.gotIt, - primary: true, - }, { - value: 'copy', - text: i18n.ts.copyErrorInfo, - }], - }); - if (result === 'copy') { - copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`); - success(); - } - return; - } else if (err.code === 'RATE_LIMIT_EXCEEDED') { - title = i18n.ts.cannotPerformTemporary; - text = i18n.ts.cannotPerformTemporaryDescription; - } else if (err.code === 'INVALID_PARAM') { - title = i18n.ts.invalidParamError; - text = i18n.ts.invalidParamErrorDescription; - } else if (err.code === 'ROLE_PERMISSION_DENIED') { - title = i18n.ts.permissionDeniedError; - text = i18n.ts.permissionDeniedErrorDescription; - } else if (err.code.startsWith('TOO_MANY')) { - title = i18n.ts.youCannotCreateAnymore; - text = `${i18n.ts.error}: ${err.id}`; - } else if (err.message.startsWith('Unexpected token')) { - title = i18n.ts.gotInvalidResponseError; - text = i18n.ts.gotInvalidResponseErrorDescription; - } - alert({ + promiseDialog(promise, onSuccess, onFailure ?? (err => apiErrorHandler(err, endpoint))); + + return promise; +}); + +export async function apiErrorHandler(err: Misskey.api.APIError, endpoint?: string): Promise { + let title: string | undefined; + let text = err.message + '\n' + err.id; + + if (err.code === 'INTERNAL_ERROR') { + title = i18n.ts.internalServerError; + text = i18n.ts.internalServerErrorDescription; + const date = new Date().toISOString(); + const { result } = await actions({ type: 'error', title, text, details: err.info, + actions: [{ + value: 'ok', + text: i18n.ts.gotIt, + primary: true, + }, { + value: 'copy', + text: i18n.ts.copyErrorInfo, + }], }); - }); + if (result === 'copy') { + copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`); + success(); + } + return; + } else if (err.code === 'RATE_LIMIT_EXCEEDED') { + title = i18n.ts.cannotPerformTemporary; + text = i18n.ts.cannotPerformTemporaryDescription; + } else if (err.code === 'INVALID_PARAM') { + title = i18n.ts.invalidParamError; + text = i18n.ts.invalidParamErrorDescription; + } else if (err.code === 'ROLE_PERMISSION_DENIED') { + title = i18n.ts.permissionDeniedError; + text = i18n.ts.permissionDeniedErrorDescription; + } else if (err.code?.startsWith('TOO_MANY')) { + title = i18n.ts.youCannotCreateAnymore; + text = `${i18n.ts.error}: ${err.id}`; + } - return promise; -}) as typeof misskeyApi; + // @ts-expect-error Misskey内部で定義されていない不明なエラー + if (!err.id && (err.statusCode ?? 0) > 499) { + title = i18n.ts.gotInvalidResponseError; + text = i18n.ts.gotInvalidResponseErrorDescription; + } + + if (err.id && !title) { + title = i18n.ts.somethingHappened; + } else if (!title) { + title = i18n.ts.somethingHappened; + text = err.message; + } + + alert({ + type: 'error', + title, + text, + // @ts-expect-error Misskeyのエラーならinfoを、そうでなければそのまま表示 + details: err.id ? err.info : err as unknown, + }); +} export function promiseDialog( promise: Promise, diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts index 49fb6f9e59c6..641966bea743 100644 --- a/packages/frontend/src/scripts/misskey-api.ts +++ b/packages/frontend/src/scripts/misskey-api.ts @@ -44,14 +44,15 @@ export function misskeyApi< }, signal, }).then(async (res) => { - const body = res.status === 204 ? null : await res.json(); - - if (res.status === 200) { + if (res.ok && res.status !== 204) { + const body = await res.json(); resolve(body); } else if (res.status === 204) { resolve(undefined as _ResT); // void -> undefined } else { - reject(body.error); + // エラー応答で JSON.parse に失敗した場合は HTTP ステータスコードとメッセージを返す + const body = await res.json().catch(() => ({ statusCode: res.status, message: res.statusText })); + reject(typeof body.error === 'object' ? body.error : body); } }).catch(reject); });