wip
This commit is contained in:
parent
230e952c84
commit
d7b77ee022
12 changed files with 136 additions and 169 deletions
|
@ -1,6 +1,7 @@
|
|||
import { Context } from 'koa';
|
||||
import { ErrorCode } from '../common/types/error-code';
|
||||
|
||||
export const die = (ctx: Context, error = '問題が発生しました。お手数ですが、最初からやり直してください。', status = 400): Promise<void> => {
|
||||
export const die = (ctx: Context, error: ErrorCode = 'other', status = 400): Promise<void> => {
|
||||
ctx.status = status;
|
||||
return ctx.render('error', { error });
|
||||
return ctx.render('frontend', { error });
|
||||
};
|
||||
|
|
|
@ -7,13 +7,8 @@ import { v4 as uuid } from 'uuid';
|
|||
import ms from 'ms';
|
||||
|
||||
import { config } from '../config';
|
||||
import { upsertUser, getUser, updateUser, updateUsersMisshaiToken, getUserByMisshaiToken, deleteUser } from './functions/users';
|
||||
import { upsertUser, getUser, updateUser, updateUsersMisshaiToken } from './functions/users';
|
||||
import { api } from './services/misskey';
|
||||
import { AlertMode, alertModes } from '../common/types/alert-mode';
|
||||
import { Users } from './models';
|
||||
import { sendAlert } from './services/send-alert';
|
||||
import { visibilities, Visibility } from '../common/types/visibility';
|
||||
import { defaultTemplate } from '../common/default-template';
|
||||
import { die } from './die';
|
||||
|
||||
export const router = new Router<DefaultState, Context>();
|
||||
|
@ -23,15 +18,14 @@ const tokenSecretCache: Record<string, string> = {};
|
|||
|
||||
router.get('/login', async ctx => {
|
||||
let host = ctx.query.host as string | undefined;
|
||||
|
||||
if (!host) {
|
||||
await die(ctx, 'host is empty');
|
||||
await die(ctx, 'invalidParamater');
|
||||
return;
|
||||
}
|
||||
const meta = await api<{ name: string, uri: string, version: string, features: Record<string, boolean | undefined> }>(host, 'meta', {});
|
||||
|
||||
const meta = await api<{ name: string, uri: string, version: string, features: Record<string, boolean | undefined> }>(host, 'meta', {});
|
||||
if (meta.version.includes('hitori')) {
|
||||
await die(ctx, 'ひとりすきーは連携できません。');
|
||||
await die(ctx, 'hitorisskeyIsDenied');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -42,7 +36,7 @@ router.get('/login', async ctx => {
|
|||
const permission = ['write:notes', 'write:notifications'];
|
||||
|
||||
if (meta.features.miauth) {
|
||||
// Use MiAuth
|
||||
// MiAuthを使用する
|
||||
const callback = encodeURI(`${config.url}/miauth`);
|
||||
|
||||
const session = uuid();
|
||||
|
@ -51,7 +45,7 @@ router.get('/login', async ctx => {
|
|||
|
||||
ctx.redirect(url);
|
||||
} else {
|
||||
// Use legacy authentication
|
||||
// 旧型認証を使用する
|
||||
const callbackUrl = encodeURI(`${config.url}/legacy-auth`);
|
||||
|
||||
const { secret } = await api<{ secret: string }>(host, 'app/create', {
|
||||
|
@ -70,13 +64,13 @@ router.get('/login', async ctx => {
|
|||
});
|
||||
|
||||
router.get('/teapot', async ctx => {
|
||||
await die(ctx, 'I\'m a teapot', 418);
|
||||
await die(ctx, 'teapot', 418);
|
||||
});
|
||||
|
||||
router.get('/miauth', async ctx => {
|
||||
const session = ctx.query.session as string | undefined;
|
||||
if (!session) {
|
||||
await die(ctx, 'session required');
|
||||
await die(ctx, 'sessionRequired');
|
||||
return;
|
||||
}
|
||||
const host = sessionHostCache[session];
|
||||
|
@ -101,7 +95,7 @@ router.get('/miauth', async ctx => {
|
|||
router.get('/legacy-auth', async ctx => {
|
||||
const token = ctx.query.token as string | undefined;
|
||||
if (!token) {
|
||||
await die(ctx, 'token required');
|
||||
await die(ctx, 'tokenRequired');
|
||||
return;
|
||||
}
|
||||
const host = sessionHostCache[token];
|
||||
|
@ -125,97 +119,6 @@ router.get('/legacy-auth', async ctx => {
|
|||
await login(ctx, user, host, i);
|
||||
});
|
||||
|
||||
router.post('/update-settings', async ctx => {
|
||||
const mode = ctx.request.body.alertMode as AlertMode;
|
||||
// 一応型チェック
|
||||
if (!alertModes.includes(mode)) {
|
||||
await die(ctx, `${mode} is an invalid value`);
|
||||
return;
|
||||
}
|
||||
const visibility = ctx.request.body.visibility as Visibility;
|
||||
// 一応型チェック
|
||||
if (!visibilities.includes(visibility)) {
|
||||
await die(ctx, `${mode} is an invalid value`);
|
||||
return;
|
||||
}
|
||||
|
||||
const flag = ctx.request.body.flag;
|
||||
|
||||
const template = ctx.request.body.template?.trim();
|
||||
|
||||
const token = ctx.cookies.get('token');
|
||||
if (!token) {
|
||||
await die(ctx, 'ログインしていません');
|
||||
return;
|
||||
}
|
||||
|
||||
const u = await getUserByMisshaiToken(token);
|
||||
|
||||
if (!u) {
|
||||
await die(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
await Users.update(u.id, {
|
||||
alertMode: mode,
|
||||
localOnly: flag === 'localOnly',
|
||||
remoteFollowersOnly: flag === 'remoteFollowersOnly',
|
||||
template: template === defaultTemplate || !template ? null : template,
|
||||
visibility,
|
||||
});
|
||||
|
||||
ctx.redirect('/?from=updateSettings');
|
||||
});
|
||||
|
||||
|
||||
|
||||
router.post('/logout', async ctx => {
|
||||
const token = ctx.cookies.get('token');
|
||||
if (!token) {
|
||||
await die(ctx, 'ログインしていません');
|
||||
return;
|
||||
}
|
||||
ctx.cookies.set('token', '');
|
||||
ctx.redirect('/?from=logout');
|
||||
});
|
||||
|
||||
router.post('/optout', async ctx => {
|
||||
const token = ctx.cookies.get('token');
|
||||
if (!token) {
|
||||
await die(ctx, 'ログインしていません');
|
||||
return;
|
||||
}
|
||||
ctx.cookies.set('token', '');
|
||||
|
||||
const u = await getUserByMisshaiToken(token);
|
||||
|
||||
if (!u) {
|
||||
await die(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteUser(u.username, u.host);
|
||||
|
||||
ctx.redirect('/?from=optout');
|
||||
});
|
||||
|
||||
router.post('/send', async ctx => {
|
||||
const token = ctx.cookies.get('token');
|
||||
if (!token) {
|
||||
await die(ctx, 'ログインしていません');
|
||||
return;
|
||||
}
|
||||
|
||||
const u = await getUserByMisshaiToken(token);
|
||||
|
||||
if (!u) {
|
||||
await die(ctx);
|
||||
return;
|
||||
}
|
||||
await sendAlert(u).catch(() => die(ctx));
|
||||
ctx.redirect('/?from=send');
|
||||
});
|
||||
|
||||
router.get('/assets/(.*)', async ctx => {
|
||||
await koaSend(ctx as any, ctx.path.replace('/assets/', ''), {
|
||||
root: `${__dirname}/../assets/`,
|
||||
|
@ -252,8 +155,5 @@ async function login(ctx: Context, user: Record<string, unknown>, host: string,
|
|||
|
||||
const misshaiToken = await updateUsersMisshaiToken(u);
|
||||
|
||||
ctx.cookies.set('token', misshaiToken, { signed: false, httpOnly: false });
|
||||
|
||||
// await ctx.render('logined', { user: u });
|
||||
ctx.redirect('/');
|
||||
await ctx.render('frontend', { token: misshaiToken });
|
||||
}
|
||||
|
|
|
@ -29,4 +29,13 @@ html
|
|||
body
|
||||
#app: .loading Loading...
|
||||
|
||||
if token
|
||||
script.
|
||||
localStorage.setItem('token', '#{token}');
|
||||
history.replaceState(null, null, '/');
|
||||
|
||||
if error
|
||||
script.
|
||||
window.__misshaialert = { error: '#{error}' };
|
||||
|
||||
script(src=`/assets/fe.${version}.js` async defer)
|
||||
|
|
11
src/common/types/error-code.ts
Normal file
11
src/common/types/error-code.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const errorCodes = [
|
||||
'hitorisskeyIsDenied',
|
||||
'teapot',
|
||||
'sessionRequired',
|
||||
'tokenRequired',
|
||||
'invalidParamater',
|
||||
'notAuthorized',
|
||||
'other',
|
||||
] as const;
|
||||
|
||||
export type ErrorCode = typeof errorCodes[number];
|
|
@ -9,10 +9,11 @@ import { Header } from './components/Header';
|
|||
import { TermPage } from './pages/term';
|
||||
import { store, useSelector } from './store';
|
||||
import { ModalComponent } from './Modal';
|
||||
import { ActualTheme } from './misc/theme';
|
||||
import { ErrorCode } from '../common/types/error-code';
|
||||
|
||||
import 'xeltica-ui/dist/css/xeltica-ui.min.css';
|
||||
import './style.scss';
|
||||
import { ActualTheme } from './misc/theme';
|
||||
|
||||
const AppInner : React.VFC = () => {
|
||||
const $location = useLocation();
|
||||
|
@ -49,8 +50,22 @@ const AppInner : React.VFC = () => {
|
|||
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
const error = (window as any).__misshaialert?.error;
|
||||
|
||||
return error ? (
|
||||
<div className="container">
|
||||
<Header hasTopLink />
|
||||
<div className="xarticle">
|
||||
<h1>{t('error')}</h1>
|
||||
<p>{t('_error.sorry')}</p>
|
||||
<p>
|
||||
{t('_error.additionalInfo')}
|
||||
{t(`_error.${error}`)}
|
||||
</p>
|
||||
<Link to="/" onClick={() => (window as any).__misshaialert.error = null}>{t('retry')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="container">
|
||||
{$location.pathname !== '/' && <Header hasTopLink />}
|
||||
<Switch>
|
||||
|
@ -64,7 +79,6 @@ const AppInner : React.VFC = () => {
|
|||
</footer>
|
||||
<ModalComponent />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { welcomeMessage } from '../misc/welcome-message';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
|
|
@ -121,7 +121,16 @@ export const SettingPage: React.VFC = () => {
|
|||
const onClickLogout = useCallback(() => {
|
||||
dispatch(showModal({
|
||||
type: 'dialog',
|
||||
message: 'WIP',
|
||||
title: 'ログアウトしてもよろしいですか?',
|
||||
message: 'ログアウトしても、アラート送信や、お使いのMisskeyアカウントのデータ収集といった機能は動作し続けます。Misskey Toolsの利用を停止したい場合は、「アカウント連携を解除する」ボタンを押下して下さい。',
|
||||
icon: 'question',
|
||||
buttons: 'yesNo',
|
||||
onSelect(i) {
|
||||
if (i === 0) {
|
||||
localStorage.removeItem(LOCALSTORAGE_KEY_TOKEN);
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
}));
|
||||
}, [dispatch]);
|
||||
|
||||
|
@ -149,16 +158,18 @@ export const SettingPage: React.VFC = () => {
|
|||
</label>
|
||||
))
|
||||
}
|
||||
{draft.alertMode === 'notification' && (
|
||||
</div>
|
||||
|
||||
{ draft.alertMode === 'notification' && (
|
||||
<div className="alert bg-danger mt-2">
|
||||
<i className="icon bi bi-exclamation-circle"></i>
|
||||
{t('_alertMode.notificationWarning')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{ draft.alertMode === 'note' && (
|
||||
<>
|
||||
<h2>{t('visibility')}</h2>
|
||||
<div>
|
||||
<label htmlFor="visibility" className="input-field">{t('visibility')}</label>
|
||||
{
|
||||
availableVisibilities.map((visibility) => (
|
||||
<label key={visibility} className="input-check">
|
||||
|
@ -169,13 +180,14 @@ export const SettingPage: React.VFC = () => {
|
|||
</label>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<label className="input-check mt-2">
|
||||
<input type="checkbox" checked={draft.localOnly} onChange={(e) => {
|
||||
updateSetting({ localOnly: e.target.checked });
|
||||
}} />
|
||||
<span>{t('localOnly')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
<Card bodyClassName="vstack">
|
||||
|
@ -218,7 +230,7 @@ export const SettingPage: React.VFC = () => {
|
|||
</ul>
|
||||
</details>
|
||||
<div className="hstack" style={{justifyContent: 'flex-end'}}>
|
||||
<button className="btn danger" onClick={() => dispatchDraft({ template: null })}>初期値に戻す</button>
|
||||
<button className="btn danger" onClick={() => dispatchDraft({ template: null })}>{t('resetToDefault')}</button>
|
||||
<button className="btn primary" onClick={() => {
|
||||
updateSettingWithDialog({ template: draft.template === '' ? null : draft.template });
|
||||
}}>{t('save')}</button>
|
||||
|
|
|
@ -4,31 +4,16 @@ import i18n from 'i18next';
|
|||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import { App } from './App';
|
||||
import { LOCALSTORAGE_KEY_TOKEN } from './const';
|
||||
import { resources } from './langs';
|
||||
import { getBrowserLanguage, resources } from './langs';
|
||||
|
||||
document.body.classList.add('dark');
|
||||
|
||||
// cookieにトークンが入ってたらlocalStorageに移し替える
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('token'))
|
||||
?.split('=')[1];
|
||||
|
||||
if (token) {
|
||||
localStorage[LOCALSTORAGE_KEY_TOKEN] = token;
|
||||
// 今の所はcookieをトークン以外に使用しないため全消去する
|
||||
// もしcookieの用途が増えるのであればここを良い感じに書き直す必要がある
|
||||
document.cookie = '';
|
||||
}
|
||||
|
||||
console.log(resources);
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
lng: localStorage['lang'] ?? 'ja_JP',
|
||||
lng: localStorage['lang'] ?? getBrowserLanguage(),
|
||||
interpolation: {
|
||||
escapeValue: false // react already safes from xss
|
||||
}
|
||||
|
|
|
@ -29,11 +29,14 @@
|
|||
failedToFetch: "Failed to fetch",
|
||||
registeredUsersCount: "Users",
|
||||
isCalculating: "It is being calculated now. Please check back later!",
|
||||
retry: "Try again",
|
||||
ok: "OK",
|
||||
yes: "Yes",
|
||||
no: "No",
|
||||
termsOfService: "Terms of Service",
|
||||
name: "Name",
|
||||
resetToDefault: "Reset to default",
|
||||
error: "Error",
|
||||
_welcomeMessage: {
|
||||
pattern1: "Overnoting Misskey?",
|
||||
pattern2: "Overusing Misskey?",
|
||||
|
@ -92,8 +95,19 @@
|
|||
system: "Follows System Preferences",
|
||||
},
|
||||
_template: {
|
||||
description: "アラートの自動投稿をカスタマイズできます。",
|
||||
description2: "ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。",
|
||||
description: "Customize template of your alert.",
|
||||
description2: "Hashtag '#misshaialert' will be appended regardless of the template.",
|
||||
},
|
||||
_error: {
|
||||
sorry: "Something went wrong. Please retry again.",
|
||||
additionalInfo: "Additional Info: ",
|
||||
hitorisskeyIsDenied: "You cannot integrate with hitorisskey.",
|
||||
teapot: "I'm a teapot.",
|
||||
sessionRequired: "Session is required.",
|
||||
tokenRequired: "Token is required.",
|
||||
invalidParameter: "Invalid parameter.",
|
||||
notAuthorized: "Not authorized.",
|
||||
other: "None",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -9,4 +9,11 @@ export const resources = {
|
|||
export const languageName = {
|
||||
'en_US': 'English',
|
||||
'ja_JP': '日本語',
|
||||
} as const;
|
||||
|
||||
export type LanguageCode = keyof typeof resources;
|
||||
|
||||
export const getBrowserLanguage = () => {
|
||||
const lang = navigator.language;
|
||||
return (Object.keys(resources) as LanguageCode[]).find(k => k.startsWith(lang)) ?? 'en_US';
|
||||
};
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
no: "いいえ",
|
||||
termsOfService: "利用規約",
|
||||
name: "名前",
|
||||
resetToDefault: "初期値に戻す",
|
||||
error: "エラー",
|
||||
retry: "やり直す",
|
||||
_welcomeMessage: {
|
||||
'pattern1': 'ついついノートしすぎていませんか?',
|
||||
'pattern2': 'Misskey, しすぎていませんか?',
|
||||
|
@ -95,5 +98,16 @@
|
|||
description: "アラートの自動投稿をカスタマイズできます。",
|
||||
description2: "ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。",
|
||||
},
|
||||
_error: {
|
||||
sorry: "問題が発生しました。お手数ですが、やり直してください。",
|
||||
additionalInfo: "追加情報: ",
|
||||
hitorisskeyIsDenied: "ひとりすきーは連携できません。",
|
||||
teapot: "I'm a teapot.",
|
||||
sessionRequired: "セッションがありません。",
|
||||
tokenRequired: "トークンがありません。",
|
||||
invalidParameter: "パラメータが不正です。",
|
||||
notAuthorized: "権限がありません。",
|
||||
other: "なし",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Tab, TabItem } from '../components/Tab';
|
|||
import { SettingPage } from '../components/SettingPage';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
||||
export const IndexSessionPage: React.VFC = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
const {t, i18n} = useTranslation();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue