Skip to content

Commit

Permalink
enhance(frontend): 定義されていないエラーも表示できるように (#738)
Browse files Browse the repository at this point in the history
+登録時のAPIエラーを表示できるように
  • Loading branch information
u1-liquid authored Sep 17, 2024
1 parent c82bf75 commit 69f31e6
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 75 deletions.
40 changes: 16 additions & 24 deletions packages/frontend/src/components/MkSignupDialog.form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ async function onSubmit(): Promise<void> {
submitting.value = true;
try {
await misskeyApi('signup', {
await os.apiWithDialog('signup', {
username: username.value,
password: password.value.password,
emailAddress: email.value,
Expand All @@ -198,35 +198,27 @@ async function onSubmit(): Promise<void> {
'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,
});
}
}
</script>
Expand Down
111 changes: 64 additions & 47 deletions packages/frontend/src/os.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,60 +34,77 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey
endpoint: E,
data: P = {} as P,
token?: string | null | undefined,
) => {
onSuccess?: ((res: Misskey.api.SwitchCaseResponseType<E, P>) => void) | null | undefined,
onFailure?: ((err: Misskey.api.APIError) => void) | null,
): Promise<Misskey.api.SwitchCaseResponseType<E, P>> => {
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<void> {
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<T>(
promise: Promise<T>,
Expand Down
9 changes: 5 additions & 4 deletions packages/frontend/src/scripts/misskey-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down

0 comments on commit 69f31e6

Please sign in to comment.