インスタンスURLのバリデーション
This commit is contained in:
parent
9d7f78856d
commit
9cc696f87a
6 changed files with 43 additions and 11 deletions
|
@ -29,7 +29,17 @@ router.get('/login', async ctx => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = await api<{ name: string, uri: string, version: string, features: Record<string, boolean | undefined> }>(host, 'meta', {});
|
// http://, https://を潰す
|
||||||
|
host = host.trim().replace(/^https?:\/\//g, '').replace(/\/+/g, '');
|
||||||
|
|
||||||
|
const meta = await api<{ name: string, uri: string, version: string, features: Record<string, boolean | undefined> }>(host, 'meta', {}).catch(async e => {
|
||||||
|
if (!(e instanceof Error && e.name === 'Error')) throw e;
|
||||||
|
await die(ctx, 'hostNotFound');
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: catchが呼ばれた場合はvoidとなるためundefinedのはず
|
||||||
|
if (typeof meta === 'undefined') return;
|
||||||
|
|
||||||
if (typeof meta !== 'object') {
|
if (typeof meta !== 'object') {
|
||||||
await die(ctx, 'other');
|
await die(ctx, 'other');
|
||||||
return;
|
return;
|
||||||
|
@ -40,9 +50,6 @@ router.get('/login', async ctx => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ホスト名の正規化
|
|
||||||
host = meta.uri.replace(/^https?:\/\//, '');
|
|
||||||
|
|
||||||
const { name, permission, description } = misskeyAppInfo;
|
const { name, permission, description } = misskeyAppInfo;
|
||||||
|
|
||||||
if (meta.features.miauth) {
|
if (meta.features.miauth) {
|
||||||
|
|
|
@ -5,6 +5,8 @@ export const errorCodes = [
|
||||||
'tokenRequired',
|
'tokenRequired',
|
||||||
'invalidParamater',
|
'invalidParamater',
|
||||||
'notAuthorized',
|
'notAuthorized',
|
||||||
|
'hostNotFound',
|
||||||
|
'invalidHostFormat',
|
||||||
'other',
|
'other',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { BrowserRouter, useLocation } from 'react-router-dom';
|
import { BrowserRouter, Link, useLocation } from 'react-router-dom';
|
||||||
import { Provider, useDispatch } from 'react-redux';
|
import { Provider, useDispatch } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -22,7 +22,18 @@ const AppInner : React.VFC = () => {
|
||||||
|
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const error = (window as any).__misshaialert?.error;
|
const [error, setError] = useState<any>((window as any).__misshaialert?.error);
|
||||||
|
|
||||||
|
// ページ遷移がまだされていないかどうか
|
||||||
|
const [isFirstView, setFirstView] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isFirstView) {
|
||||||
|
setFirstView(false);
|
||||||
|
} else if (!isFirstView && error) {
|
||||||
|
setError(null);
|
||||||
|
}
|
||||||
|
}, [$location]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const qMobile = window.matchMedia(`(max-width: ${BREAKPOINT_SM})`);
|
const qMobile = window.matchMedia(`(max-width: ${BREAKPOINT_SM})`);
|
||||||
|
@ -47,6 +58,7 @@ const AppInner : React.VFC = () => {
|
||||||
{t('_error.additionalInfo')}
|
{t('_error.additionalInfo')}
|
||||||
{t(`_error.${error}`)}
|
{t(`_error.${error}`)}
|
||||||
</p>
|
</p>
|
||||||
|
<Link to="/" className="btn primary">{t('retry')}</Link>
|
||||||
</div>
|
</div>
|
||||||
) : <Router />}
|
) : <Router />}
|
||||||
<footer className="text-center pa-5">
|
<footer className="text-center pa-5">
|
||||||
|
|
|
@ -11,6 +11,10 @@ export const LoginForm: React.VFC = () => {
|
||||||
const [host, setHost] = useState('');
|
const [host, setHost] = useState('');
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const login = () => {
|
||||||
|
location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav>
|
<nav>
|
||||||
<div>
|
<div>
|
||||||
|
@ -21,14 +25,18 @@ export const LoginForm: React.VFC = () => {
|
||||||
className="input-field"
|
className="input-field"
|
||||||
type="text"
|
type="text"
|
||||||
value={host}
|
value={host}
|
||||||
|
placeholder={t('instanceUrlPlaceholder')}
|
||||||
onChange={(e) => setHost(e.target.value)}
|
onChange={(e) => setHost(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') login();
|
||||||
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className={!host ? 'btn' : 'btn primary'}
|
className={!host ? 'btn' : 'btn primary'}
|
||||||
style={{ width: 128 }}
|
style={{ width: 128 }}
|
||||||
disabled={!host}
|
disabled={!host}
|
||||||
onClick={() => location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`}
|
onClick={login}
|
||||||
>
|
>
|
||||||
{t('login')}
|
{t('login')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"update": "更新する",
|
"update": "更新する",
|
||||||
"shareMisskeyTools": "#MisskeyTools をシェアする",
|
"shareMisskeyTools": "#MisskeyTools をシェアする",
|
||||||
"shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://misskey.tools",
|
"shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://misskey.tools",
|
||||||
|
"instanceUrlPlaceholder": "例:misskey.io",
|
||||||
"_sidebar": {
|
"_sidebar": {
|
||||||
"dashboard": "ダッシュボード",
|
"dashboard": "ダッシュボード",
|
||||||
"tools": "ツール",
|
"tools": "ツール",
|
||||||
|
@ -158,7 +159,8 @@
|
||||||
"tokenRequired": "トークンがありません。",
|
"tokenRequired": "トークンがありません。",
|
||||||
"invalidParameter": "パラメータが不正です。",
|
"invalidParameter": "パラメータが不正です。",
|
||||||
"notAuthorized": "権限がありません。",
|
"notAuthorized": "権限がありません。",
|
||||||
"other": "なし"
|
"hostNotFound": "インスタンスに接続できませんでした。ホスト名が正しく入力されているか、接続先のインスタンスが正常に動作しているかご確認ください。",
|
||||||
|
"other": "不明なエラーです。"
|
||||||
},
|
},
|
||||||
"_sendTest": {
|
"_sendTest": {
|
||||||
"title": "アラートをテスト送信しますか?",
|
"title": "アラートをテスト送信しますか?",
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { Skeleton } from '../../components/Skeleton';
|
||||||
import './misshai.scss';
|
import './misshai.scss';
|
||||||
import { Ranking } from '../../components/Ranking';
|
import { Ranking } from '../../components/Ranking';
|
||||||
import { useTitle } from '../../hooks/useTitle';
|
import { useTitle } from '../../hooks/useTitle';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const variables = [
|
const variables = [
|
||||||
'notesCount',
|
'notesCount',
|
||||||
|
@ -236,8 +237,8 @@ export const MisshaiPage: React.VFC = () => {
|
||||||
<div className="card misshaiRanking">
|
<div className="card misshaiRanking">
|
||||||
<div className="body">
|
<div className="body">
|
||||||
<h1><i className="bi bi-bar-chart"></i> {t('_missHai.ranking')}</h1>
|
<h1><i className="bi bi-bar-chart"></i> {t('_missHai.ranking')}</h1>
|
||||||
<Ranking limit={limit} />
|
<Ranking limit={10} />
|
||||||
{limit && <button className="btn primary" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</button>}
|
<Link to="/apps/miss-hai/ranking" className="btn primary" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</Link>
|
||||||
<label className="input-check mt-2">
|
<label className="input-check mt-2">
|
||||||
<input type="checkbox" checked={draft.useRanking} onChange={(e) => {
|
<input type="checkbox" checked={draft.useRanking} onChange={(e) => {
|
||||||
updateSetting({ useRanking: e.target.checked });
|
updateSetting({ useRanking: e.target.checked });
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue