Compare commits

...

12 Commits

Author SHA1 Message Date
syuilo b1ec04371a Update package.json 2023-09-01 14:28:54 +09:00
syuilo b3b968895b wip 2023-09-01 14:23:17 +09:00
syuilo 05bfb6cec6 Update tsconfig.json 2023-09-01 13:46:07 +09:00
syuilo e70df49a98 wip 2023-09-01 13:40:08 +09:00
syuilo f9a230dcaa Update test-utils.ts 2023-09-01 13:33:24 +09:00
syuilo fb0e678392 wip 2023-09-01 13:29:53 +09:00
syuilo b51eccb9e4 wip 2023-09-01 13:19:08 +09:00
syuilo 5d1fdf0922 Merge branch 'develop' into update-deps 2023-09-01 13:08:38 +09:00
syuilo f75ee1eef0 enhance(frontend): ノート詳細ページ読み込み時に前後のノートの存在チェックを行わなず、常に存在すると仮定するようにしてパフォーマンスを向上 2023-09-01 09:36:19 +09:00
dependabot[bot] 23102a2c08
build(deps): bump docker/setup-buildx-action from 2.9.1 to 2.10.0 (#11769)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.1 to 2.10.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.1...v2.10.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-29 21:17:02 +09:00
syuilo f12cdf1260
Update CHANGELOG.md 2023-08-28 18:42:18 +09:00
syuilo 257c4fccf1
feat: Refine 2fa (#11766)
* wip

* Update 2fa.qrdialog.vue

* Update 2fa.vue

* Update CHANGELOG.md

* tweak

* ✌️
2023-08-28 18:25:31 +09:00
50 changed files with 2449 additions and 2865 deletions

View File

@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v3.6.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.9.1
uses: docker/setup-buildx-action@v2.10.0
with:
platforms: linux/amd64,linux/arm64
- name: Docker meta

View File

@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v3.6.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.9.1
uses: docker/setup-buildx-action@v2.10.0
with:
platforms: linux/amd64,linux/arm64
- name: Docker meta

View File

@ -21,12 +21,14 @@
- お知らせのバナー表示やダイアログ表示が可能に
- お知らせのアイコンを設定可能に
- チャンネルをセンシティブ指定できるようになりました
- 二要素認証のバックアップコードが生成されるようになりました ref. https://github.com/MisskeyIO/misskey/pull/121
### Client
- プロフィールにその人が作ったPlayの一覧出せるように
- メニューのスイッチの動作を改善
- 絵文字ピッカーの検索の表示件数を100件に増加
- 投稿フォームのプレビューの表示状態を記憶するように
- ノート詳細ページ読み込み時のパフォーマンスを改善
- Enhance: ユーザーメニューでスイッチでユーザーリストに追加・削除できるように
- Enhance: 自分が押したリアクションのデザインを改善
- Enhance: ノート検索にローカルのみ検索可能なオプションの追加

9
locales/index.d.ts vendored
View File

@ -414,6 +414,7 @@ export interface Locale {
"administrator": string;
"token": string;
"2fa": string;
"setupOf2fa": string;
"totp": string;
"totpDescription": string;
"moderator": string;
@ -1811,9 +1812,10 @@ export interface Locale {
"step1": string;
"step2": string;
"step2Click": string;
"step2Url": string;
"step2Uri": string;
"step3Title": string;
"step3": string;
"setupCompleted": string;
"step4": string;
"securityKeyNotSupported": string;
"registerTOTPBeforeKey": string;
@ -1829,6 +1831,11 @@ export interface Locale {
"renewTOTPConfirm": string;
"renewTOTPOk": string;
"renewTOTPCancel": string;
"checkBackupCodesBeforeCloseThisWizard": string;
"backupCodes": string;
"backupCodesDescription": string;
"backupCodeUsedWarning": string;
"backupCodesExhaustedWarning": string;
};
"_permissions": {
"read:account": string;

View File

@ -411,6 +411,7 @@ aboutMisskey: "Misskeyについて"
administrator: "管理者"
token: "確認コード"
2fa: "二要素認証"
setupOf2fa: "二要素認証のセットアップ"
totp: "認証アプリ"
totpDescription: "認証アプリを使ってワンタイムパスワードを入力"
moderator: "モデレーター"
@ -1729,10 +1730,11 @@ _2fa:
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
step2: "次に、表示されているQRコードをアプリでスキャンします。"
step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。"
step2Url: "デスクトップアプリでは次のURIを入力します:"
step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します"
step3Title: "確認コードを入力"
step3: "アプリに表示されている確認コード(トークン)を入力して完了です。"
step4: "これからログインするときも、同じように確認コードを入力します。"
step3: "アプリに表示されている確認コード(トークン)を入力します。"
setupCompleted: "設定が完了しました"
step4: "これからログインするときも、同じようにコードを入力します。"
securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。"
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。"
@ -1744,9 +1746,14 @@ _2fa:
removeKeyConfirm: "{name}を削除しますか?"
whyTOTPOnlyRenew: "セキュリティキーが登録されている場合、認証アプリの設定は解除できません。"
renewTOTP: "認証アプリを再設定"
renewTOTPConfirm: "今までの認証アプリの確認コードは使用できなくなります"
renewTOTPConfirm: "今までの認証アプリの確認コードおよびバックアップコードは使用できなくなります"
renewTOTPOk: "再設定する"
renewTOTPCancel: "やめておく"
checkBackupCodesBeforeCloseThisWizard: "このウィザードを閉じる前に、以下のバックアップコードを確認してください。"
backupCodes: "バックアップコード"
backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。"
backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。"
backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。"
_permissions:
"read:account": "アカウントの情報を見る"

View File

@ -56,10 +56,10 @@
"devDependencies": {
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "6.4.1",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"cross-env": "7.0.3",
"cypress": "12.17.2",
"cypress": "13.1.0",
"eslint": "8.48.0",
"start-server-and-test": "2.0.0"
},

View File

@ -0,0 +1,11 @@
export class User2faBackupCodes1690569881926 {
name = 'User2faBackupCodes1690569881926'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ADD "twoFactorBackupSecret" character varying array`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twoFactorBackupSecret"`);
}
}

View File

@ -71,13 +71,13 @@
"@fastify/multipart": "7.7.3",
"@fastify/static": "6.10.2",
"@fastify/view": "8.0.0",
"@nestjs/common": "10.2.1",
"@nestjs/core": "10.2.1",
"@nestjs/testing": "10.2.1",
"@nestjs/common": "10.2.4",
"@nestjs/core": "10.2.4",
"@nestjs/testing": "10.2.4",
"@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "11.1.0",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.80",
"@swc/core": "1.3.82",
"accepts": "1.3.8",
"ajv": "8.12.0",
"archiver": "6.0.0",
@ -96,7 +96,7 @@
"content-disposition": "0.5.4",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fastify": "4.21.0",
"fastify": "4.22.1",
"feed": "4.2.2",
"file-type": "18.5.0",
"fluent-ffmpeg": "2.1.2",
@ -112,7 +112,7 @@
"js-yaml": "4.1.0",
"jsdom": "22.1.0",
"json5": "2.2.3",
"jsonld": "8.2.0",
"jsonld": "8.2.1",
"jsrsasign": "10.8.6",
"meilisearch": "0.34.1",
"mfm-js": "0.23.3",
@ -153,7 +153,7 @@
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
"systeminformation": "5.20.0",
"systeminformation": "5.21.3",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
"tsc-alias": "1.8.7",
@ -163,7 +163,7 @@
"typescript": "5.2.2",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.4",
"web-push": "3.6.5",
"ws": "8.13.0",
"xev": "3.0.2"
},
@ -172,7 +172,7 @@
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.5",
"@types/archiver": "5.3.2",
"@types/bcryptjs": "2.4.2",
"@types/bcryptjs": "2.4.3",
"@types/body-parser": "1.19.2",
"@types/cbor": "6.0.0",
"@types/color-convert": "2.0.0",
@ -186,7 +186,7 @@
"@types/jsrsasign": "10.5.8",
"@types/mime-types": "2.1.1",
"@types/ms": "0.7.31",
"@types/node": "20.5.6",
"@types/node": "20.5.7",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.9",
"@types/oauth": "0.9.1",
@ -195,12 +195,12 @@
"@types/pg": "8.10.2",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.5.1",
"@types/qrcode": "1.5.2",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0",
"@types/semver": "7.5.1",
"@types/sharp": "0.32.0",
"@types/simple-oauth2": "5.0.4",
"@types/sinonjs__fake-timers": "8.1.2",
@ -209,8 +209,8 @@
"@types/vary": "1.1.0",
"@types/web-push": "3.6.0",
"@types/ws": "8.5.5",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "6.4.1",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3",
"eslint": "8.48.0",

View File

@ -434,6 +434,7 @@ export class UserEntityService implements OnModuleInit {
preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
hideOnlineStatus: user.hideOnlineStatus,
hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
where: { userId: user.id, isSpecified: true },

View File

@ -20,7 +20,7 @@ const round = (num: number) => Math.round(num * 10) / 10;
@Injectable()
export class ServerStatsService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer | null = null;
private intervalId: NodeJS.Timeout | null = null;
constructor(
private metaService: MetaService,

View File

@ -101,6 +101,11 @@ export class MiUserProfile {
})
public twoFactorSecret: string | null;
@Column('varchar', {
nullable: true, array: true,
})
public twoFactorBackupSecret: string[] | null;
@Column('boolean', {
default: false,
})

View File

@ -321,6 +321,11 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
twoFactorBackupCodesStock: {
type: 'string',
enum: ['full', 'partial', 'none'],
nullable: false, optional: false,
},
hideOnlineStatus: {
type: 'boolean',
nullable: false, optional: false,

View File

@ -160,6 +160,13 @@ export class SigninApiService {
});
}
if (profile.twoFactorBackupSecret?.includes(token)) {
await this.userProfilesRepository.update({ userId: profile.userId }, {
twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token),
});
return this.signinService.signin(request, reply, user);
}
const delta = OTPAuth.TOTP.validate({
secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!),
digits: 6,

View File

@ -54,8 +54,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('not verified');
}
const backupCodes = Array.from({ length: 5 }, () => new OTPAuth.Secret().base32);
await this.userProfilesRepository.update(me.id, {
twoFactorSecret: profile.twoFactorTempSecret,
twoFactorBackupSecret: backupCodes,
twoFactorEnabled: true,
});
@ -64,6 +67,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
detail: true,
includeSecrets: true,
}));
return {
backupCodes: backupCodes,
};
});
}
}

View File

@ -46,6 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userProfilesRepository.update(me.id, {
twoFactorSecret: null,
twoFactorBackupSecret: null,
twoFactorEnabled: false,
usePasswordLessLogin: false,
});

View File

@ -35,7 +35,7 @@ export default class Connection {
public userIdsWhoMeMuting: Set<string> = new Set();
public userIdsWhoBlockingMe: Set<string> = new Set();
public userIdsWhoMeMutingRenotes: Set<string> = new Set();
private fetchIntervalId: NodeJS.Timer | null = null;
private fetchIntervalId: NodeJS.Timeout | null = null;
constructor(
private channelsService: ChannelsService,

View File

@ -37,7 +37,6 @@ import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { RoleService } from '@/core/RoleService.js';
import manifest from './manifest.json' assert { type: 'json' };
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
@ -52,6 +51,45 @@ const assets = `${_dirname}/../../../../../built/_frontend_dist_/`;
const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
const viteOut = `${_dirname}/../../../../../built/_vite_/`;
const manifest = {
'short_name': 'Misskey',
'name': 'Misskey',
'start_url': '/',
'display': 'standalone',
'background_color': '#313a42',
'theme_color': '#86b300',
'icons': [
{
'src': '/static-assets/icons/192.png',
'sizes': '192x192',
'type': 'image/png',
'purpose': 'maskable',
},
{
'src': '/static-assets/icons/512.png',
'sizes': '512x512',
'type': 'image/png',
'purpose': 'maskable',
},
{
'src': '/static-assets/splash.png',
'sizes': '300x300',
'type': 'image/png',
'purpose': 'any',
},
],
'share_target': {
'action': '/share/',
'method': 'GET',
'enctype': 'application/x-www-form-urlencoded',
'params': {
'title': 'title',
'text': 'text',
'url': 'url',
},
},
};
@Injectable()
export class ClientServerService {
private logger: Logger;

View File

@ -7,15 +7,15 @@ doctype html
//
-
_____ _ _
| |_|___ ___| |_ ___ _ _
_____ _ _
| |_|___ ___| |_ ___ _ _
| | | | |_ -|_ -| '_| -_| | |
|_|_|_|_|___|___|_,_|___|_ |
|___|
Thank you for using Misskey!
If you are reading this message... how about joining the development?
https://github.com/misskey-dev/misskey
html
@ -35,7 +35,7 @@ html
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.25.0')
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.32.0')
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
if !config.clientManifestExists

View File

@ -191,7 +191,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const usersShowResponse = await api('/users/show', {
username,
@ -216,7 +216,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@ -272,7 +272,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@ -329,7 +329,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@ -371,7 +371,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@ -423,7 +423,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const usersShowResponse = await api('/users/show', {
username,

View File

@ -152,6 +152,7 @@ describe('ユーザー', () => {
preventAiLearning: user.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
twoFactorBackupCodesStock: user.twoFactorBackupCodesStock,
hideOnlineStatus: user.hideOnlineStatus,
hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes,
hasUnreadMentions: user.hasUnreadMentions,
@ -398,6 +399,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.preventAiLearning, true);
assert.strictEqual(response.isExplorable, true);
assert.strictEqual(response.isDeleted, false);
assert.strictEqual(response.twoFactorBackupCodesStock, 'none');
assert.strictEqual(response.hideOnlineStatus, false);
assert.strictEqual(response.hasUnreadSpecifiedNotes, false);
assert.strictEqual(response.hasUnreadMentions, false);

View File

@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": true,
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true,
"removeComments": false,
"noLib": false,

View File

@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": false,
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true,
"removeComments": false,
"noLib": false,
@ -33,8 +33,9 @@
"node"
],
"typeRoots": [
"./src/@types",
"./node_modules/@types",
"./src/@types"
"./node_modules"
],
"lib": [
"esnext"

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import path from 'node:path';

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { entities } from 'misskey-js'
export function abuseUserReport() {
@ -110,6 +115,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
publicReactions: false,
securityKeys: false,
twoFactorEnabled: false,
twoFactorBackupCodesStock: 'none',
updatedAt: null,
uri: null,
url: null,

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { existsSync, readFileSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { basename, dirname } from 'node:path/posix';

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { StorybookConfig } from '@storybook/vue3-vite';

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { addons } from '@storybook/manager-api';
import { create } from '@storybook/theming/create';

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { type SharedOptions, rest } from 'msw';
export const onUnhandledRequest = ((req, print) => {

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { writeFile } from 'node:fs/promises';
import locales from '../../../locales/index.js';

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { readFile, writeFile } from 'node:fs/promises';
import JSON5 from 'json5';

View File

@ -1,6 +1,6 @@
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.21.0/tabler-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.32.0/tabler-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
<style>
html {

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { addons } from '@storybook/addons';
import { FORCE_REMOUNT } from '@storybook/core-events';
import { type Preview, setup } from '@storybook/vue3';

View File

@ -6,6 +6,7 @@
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noEmitOnError": false,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,

View File

@ -5,7 +5,7 @@
"watch": "vite",
"build": "vite build",
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
"build-storybook-pre": "tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
"build-storybook": "pnpm build-storybook-pre && storybook build",
"chromatic": "chromatic",
"test": "vitest --run",
@ -21,9 +21,9 @@
"@rollup/plugin-replace": "5.0.2",
"@rollup/pluginutils": "5.0.4",
"@syuilo/aiscript": "0.15.0",
"@tabler/icons-webfont": "2.30.0",
"@vitejs/plugin-vue": "4.3.3",
"@vue-macros/reactivity-transform": "0.3.21",
"@tabler/icons-webfont": "2.32.0",
"@vitejs/plugin-vue": "4.3.4",
"@vue-macros/reactivity-transform": "0.3.22",
"@vue/compiler-sfc": "3.3.4",
"astring": "1.8.6",
"autosize": "6.0.1",
@ -36,7 +36,7 @@
"chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1",
"chromatic": "6.19.9",
"chromatic": "6.24.1",
"compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4",
"date-fns": "2.30.0",
@ -62,7 +62,7 @@
"strict-event-emitter-types": "2.0.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.155.0",
"three": "0.156.0",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.7",
@ -77,25 +77,24 @@
"vuedraggable": "next"
},
"devDependencies": {
"@storybook/addon-actions": "7.0.27",
"@storybook/addon-essentials": "7.0.27",
"@storybook/addon-interactions": "7.0.27",
"@storybook/addon-links": "7.0.27",
"@storybook/addon-storysource": "7.0.27",
"@storybook/addons": "7.0.27",
"@storybook/blocks": "7.0.27",
"@storybook/core-events": "7.0.27",
"@storybook/jest": "0.1.0",
"@storybook/manager-api": "7.0.27",
"@storybook/preview-api": "7.0.27",
"@storybook/react": "7.0.27",
"@storybook/react-vite": "7.0.27",
"@storybook/addon-actions": "7.4.0",
"@storybook/addon-essentials": "7.4.0",
"@storybook/addon-interactions": "7.4.0",
"@storybook/addon-links": "7.4.0",
"@storybook/addon-storysource": "7.4.0",
"@storybook/addons": "7.4.0",
"@storybook/blocks": "7.4.0",
"@storybook/core-events": "7.4.0",
"@storybook/jest": "0.2.2",
"@storybook/manager-api": "7.4.0",
"@storybook/preview-api": "7.4.0",
"@storybook/react": "7.4.0",
"@storybook/react-vite": "7.4.0",
"@storybook/testing-library": "0.2.0",
"@storybook/theming": "7.0.27",
"@storybook/types": "7.0.27",
"@storybook/vue3": "7.0.27",
"@storybook/vue3-vite": "7.0.27",
"@testing-library/jest-dom": "5.16.5",
"@storybook/theming": "7.4.0",
"@storybook/types": "7.4.0",
"@storybook/vue3": "7.4.0",
"@storybook/vue3-vite": "7.4.0",
"@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1",
"@types/estree": "1.0.1",
@ -103,36 +102,35 @@
"@types/gulp-rename": "2.0.2",
"@types/matter-js": "0.19.0",
"@types/micromatch": "4.0.2",
"@types/node": "20.5.6",
"@types/node": "20.5.7",
"@types/punycode": "2.1.0",
"@types/sanitize-html": "2.9.0",
"@types/testing-library__jest-dom": "5.14.9",
"@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "9.0.2",
"@types/uuid": "9.0.3",
"@types/websocket": "1.0.6",
"@types/ws": "8.5.5",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "6.4.1",
"@vitest/coverage-v8": "0.34.0",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"@vitest/coverage-v8": "0.34.3",
"@vue/runtime-core": "3.3.4",
"acorn": "8.10.0",
"cross-env": "7.0.3",
"cypress": "12.17.2",
"cypress": "13.1.0",
"eslint": "8.48.0",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-vue": "9.17.0",
"fast-glob": "3.3.1",
"happy-dom": "10.0.3",
"micromatch": "4.0.5",
"msw": "1.2.4",
"msw": "1.2.5",
"msw-storybook-addon": "1.8.0",
"nodemon": "3.0.1",
"prettier": "3.0.2",
"prettier": "3.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"start-server-and-test": "2.0.0",
"storybook": "7.0.27",
"storybook": "7.4.0",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.2",

View File

@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.password }}</template>
<template #prefix><i class="ti ti-lock"></i></template>
</MkInput>
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="one-time-code" :spellcheck="false" required>
<MkInput v-model="token" type="text" pattern="^([0-9]{6}|[A-Z0-9]{32})$" autocomplete="one-time-code" :spellcheck="false" required>
<template #label>{{ i18n.ts.token }}</template>
<template #prefix><i class="ti ti-123"></i></template>
</MkInput>

View File

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div class="_margin">
<MkButton v-if="!showNext && hasNext" :class="$style.loadNext" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton>
<MkButton v-if="!showNext" :class="$style.loadNext" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton>
<div class="_margin _gaps_s">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
<MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note"/>
@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA>
</div>
</div>
<MkButton v-if="!showPrev && hasPrev" :class="$style.loadPrev" @click="showPrev = true"><i class="ti ti-chevron-down"></i></MkButton>
<MkButton v-if="!showPrev" :class="$style.loadPrev" @click="showPrev = true"><i class="ti ti-chevron-down"></i></MkButton>
</div>
<div v-if="showPrev" class="_margin">
@ -63,8 +63,6 @@ const props = defineProps<{
let note = $ref<null | misskey.entities.Note>();
let clips = $ref();
let hasPrev = $ref(false);
let hasNext = $ref(false);
let showPrev = $ref(false);
let showNext = $ref(false);
let error = $ref();
@ -89,8 +87,6 @@ const nextPagination = {
};
function fetchNote() {
hasPrev = false;
hasNext = false;
showPrev = false;
showNext = false;
note = null;
@ -102,20 +98,8 @@ function fetchNote() {
os.api('notes/clips', {
noteId: note.id,
}),
os.api('users/notes', {
userId: note.userId,
untilId: note.id,
limit: 1,
}),
os.api('users/notes', {
userId: note.userId,
sinceId: note.id,
limit: 1,
}),
]).then(([_clips, prev, next]) => {
]).then(([_clips]) => {
clips = _clips;
hasPrev = prev.length !== 0;
hasNext = next.length !== 0;
});
}).catch(err => {
error = err;

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="800">
<div :class="$style.root">
<div :class="$style.editor" class="_panel">
<PrismEditor v-model="code" class="_code code" :highlight="highlighter" :lineNumbers="false"/>
<PrismEditor v-model="code" class="_monospace" :class="$style.code" :highlight="highlighter" :lineNumbers="false"/>
<MkButton style="position: absolute; top: 8px; right: 8px;" primary @click="run()"><i class="ti ti-player-play"></i></MkButton>
</div>
@ -175,6 +175,14 @@ definePageMetadata({
position: relative;
}
.code {
background: #2d2d2d;
color: #ccc;
font-size: 14px;
line-height: 1.5;
padding: 5px;
}
.ui {
padding: 32px;
}

View File

@ -4,45 +4,110 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModal
ref="dialogEl"
:preferType="'dialog'"
:zPriority="'low'"
@click="cancel"
<MkModalWindow
ref="dialog"
:width="500"
:height="550"
@close="cancel"
@closed="emit('closed')"
>
<div :class="$style.root" class="_gaps_m">
<I18n :src="i18n.ts._2fa.step1" tag="div">
<template #a>
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
<template #header>{{ i18n.ts.setupOf2fa }}</template>
<div style="overflow-x: clip;">
<Transition
mode="out-in"
:enterActiveClass="$style.transition_x_enterActive"
:leaveActiveClass="$style.transition_x_leaveActive"
:enterFromClass="$style.transition_x_enterFrom"
:leaveToClass="$style.transition_x_leaveTo"
>
<template v-if="page === 0">
<div style="height: 100cqh; overflow: auto; text-align: center;">
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps">
<I18n :src="i18n.ts._2fa.step1" tag="div">
<template #a>
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
</template>
<template #b>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
</template>
</I18n>
<div>{{ i18n.ts._2fa.step2 }}<br>{{ i18n.ts._2fa.step2Click }}</div>
<a :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a>
<MkKeyValue :copy="twoFactorData.url">
<template #key>{{ i18n.ts._2fa.step2Uri }}</template>
<template #value>{{ twoFactorData.url }}</template>
</MkKeyValue>
</div>
<div class="_buttonsCenter" style="margin-top: 16px;">
<MkButton rounded @click="cancel">{{ i18n.ts.cancel }}</MkButton>
<MkButton primary rounded gradate @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
</div>
</MkSpacer>
</div>
</template>
<template #b>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
<template v-else-if="page === 1">
<div style="height: 100cqh; overflow: auto;">
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps">
<div>{{ i18n.ts._2fa.step3Title }}</div>
<MkInput v-model="token" autocomplete="one-time-code" type="number"></MkInput>
<div>{{ i18n.ts._2fa.step3 }}</div>
</div>
<div class="_buttonsCenter" style="margin-top: 16px;">
<MkButton rounded @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
<MkButton primary rounded gradate @click="tokenDone">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
</div>
</MkSpacer>
</div>
</template>
</I18n>
<div>
{{ i18n.ts._2fa.step2 }}<br>
{{ i18n.ts._2fa.step2Click }}
</div>
<a :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a>
<MkKeyValue :copy="twoFactorData.url">
<template #key>{{ i18n.ts._2fa.step2Url }}</template>
<template #value>{{ twoFactorData.url }}</template>
</MkKeyValue>
<div class="_buttons">
<MkButton primary @click="ok">{{ i18n.ts.next }}</MkButton>
<MkButton @click="cancel">{{ i18n.ts.cancel }}</MkButton>
</div>
<template v-else-if="page === 2">
<div style="height: 100cqh; overflow: auto;">
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps">
<div style="text-align: center;">{{ i18n.ts._2fa.setupCompleted }}🎉</div>
<div style="text-align: center;">{{ i18n.ts._2fa.step4 }}</div>
<div style="text-align: center; font-weight: bold;">{{ i18n.ts._2fa.checkBackupCodesBeforeCloseThisWizard }}</div>
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts._2fa.backupCodes }}</template>
<div class="_gaps">
<MkInfo warn>{{ i18n.ts._2fa.backupCodesDescription }}</MkInfo>
<div v-for="(code, i) in backupCodes" :key="code" class="_gaps_s">
<MkKeyValue :copy="code">
<template #key>#{{ i + 1 }}</template>
<template #value><code class="_monospace">{{ code }}</code></template>
</MkKeyValue>
</div>
</div>
</MkFolder>
</div>
<div class="_buttonsCenter" style="margin-top: 16px;">
<MkButton primary rounded gradate @click="allDone">{{ i18n.ts.done }}</MkButton>
</div>
</MkSpacer>
</div>
</template>
</Transition>
</div>
</MkModal>
</MkModalWindow>
</template>
<script lang="ts" setup>
import { shallowRef, ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkModal from '@/components/MkModal.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkInput from '@/components/MkInput.vue';
import { i18n } from '@/i18n';
import * as os from '@/os';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import { confetti } from '@/scripts/confetti';
defineProps<{
twoFactorData: {
@ -52,36 +117,53 @@ defineProps<{
}>();
const emit = defineEmits<{
(ev: 'ok'): void;
(ev: 'cancel'): void;
(ev: 'closed'): void;
}>();
const cancel = () => {
emit('cancel');
emit('closed');
};
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const page = ref(0);
const token = ref<string | number | null>(null);
const backupCodes = ref<string[]>();
const ok = () => {
emit('ok');
emit('closed');
};
function cancel() {
dialog.value.close();
}
async function tokenDone() {
const res = await os.apiWithDialog('i/2fa/done', {
token: token.value.toString(),
});
backupCodes.value = res.backupCodes;
page.value++;
confetti({
duration: 1000 * 3,
});
}
function allDone() {
dialog.value.close();
}
</script>
<style lang="scss" module>
.root {
position: relative;
margin: auto;
padding: 32px;
min-width: 320px;
max-width: calc(100svw - 64px);
box-sizing: border-box;
background: var(--panel);
border-radius: var(--radius);
.transition_x_enterActive,
.transition_x_leaveActive {
transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
}
.transition_x_enterFrom {
opacity: 0;
transform: translateX(50px);
}
.transition_x_leaveTo {
opacity: 0;
transform: translateX(-50px);
}
.qr {
width: 20em;
max-width: 100%;
width: 200px;
max-width: 100%;
}
</style>

View File

@ -8,20 +8,28 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts['2fa'] }}</template>
<div v-if="$i" class="_gaps_s">
<MkFolder>
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'partial'" warn>
{{ i18n.ts._2fa.backupCodeUsedWarning }}
</MkInfo>
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'none'" warn>
{{ i18n.ts._2fa.backupCodesExhaustedWarning }}
</MkInfo>
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-shield-lock"></i></template>
<template #label>{{ i18n.ts.totp }}</template>
<template #caption>{{ i18n.ts.totpDescription }}</template>
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
<template v-if="$i.securityKeysList.length > 0">
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
</template>
<MkButton v-else @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
<MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
</div>
<MkButton v-else-if="!twoFactorData && !$i.twoFactorEnabled" @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
<MkButton v-else-if="!$i.twoFactorEnabled" primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
</MkFolder>
<MkFolder>
@ -85,7 +93,6 @@ withDefaults(defineProps<{
first: false,
});
const twoFactorData = ref<any>(null);
const supportsCredentials = ref(!!navigator.credentials);
const usePasswordLessLogin = $computed(() => $i!.usePasswordLessLogin);
@ -102,31 +109,9 @@ async function registerTOTP() {
password: password.result,
});
const qrdialog = await new Promise<boolean>(res => {
os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), {
twoFactorData,
}, {
'ok': () => res(true),
'cancel': () => res(false),
}, 'closed');
});
if (!qrdialog) return;
const token = await os.inputNumber({
title: i18n.ts._2fa.step3Title,
text: i18n.ts._2fa.step3,
autocomplete: 'one-time-code',
});
if (token.canceled) return;
await os.apiWithDialog('i/2fa/done', {
token: token.result.toString(),
});
await os.alert({
type: 'success',
text: i18n.ts._2fa.step4,
});
os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), {
twoFactorData,
}, {}, 'closed');
}
function unregisterTOTP() {

View File

@ -3,8 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
/// <reference types="@testing-library/jest-dom"/>
export async function tick(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never);

View File

@ -400,15 +400,6 @@ hr {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace !important;
}
._code {
@extend ._monospace;
background: #2d2d2d;
color: #ccc;
font-size: 14px;
line-height: 1.5;
padding: 5px;
}
.prism-editor__textarea:focus {
outline: none;
}

View File

@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": true,
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true,
"removeComments": false,
"noLib": false,

View File

@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": false,
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"removeComments": false,
"noLib": false,
"strict": true,
@ -26,9 +26,10 @@
"@/*": ["./src/*"]
},
"typeRoots": [
"node_modules/@types",
"node_modules/@vue-macros",
"@types"
"./@types",
"./node_modules/@types",
"./node_modules/@vue-macros",
"./node_modules"
],
"types": [
"vite/client",

View File

@ -2462,6 +2462,7 @@ type MeDetailed = UserDetailed & {
receiveAnnouncementEmail: boolean;
usePasswordLessLogin: boolean;
unreadAnnouncements: Announcement[];
twoFactorBackupCodesStock: 'full' | 'partial' | 'none';
[other: string]: any;
};

View File

@ -23,15 +23,15 @@
"@microsoft/api-extractor": "7.36.4",
"@swc/jest": "0.2.29",
"@types/jest": "29.5.4",
"@types/node": "20.5.6",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "6.4.1",
"@types/node": "20.5.7",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"eslint": "8.48.0",
"jest": "29.6.4",
"jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.4.1",
"mock-socket": "9.2.1",
"tsd": "0.28.1",
"tsd": "0.29.0",
"typescript": "5.2.2"
},
"files": [
@ -39,7 +39,7 @@
],
"dependencies": {
"@swc/cli": "0.1.62",
"@swc/core": "1.3.80",
"@swc/core": "1.3.82",
"eventemitter3": "5.0.1",
"reconnecting-websocket": "4.4.0"
}

View File

@ -105,6 +105,7 @@ export type MeDetailed = UserDetailed & {
receiveAnnouncementEmail: boolean;
usePasswordLessLogin: boolean;
unreadAnnouncements: Announcement[];
twoFactorBackupCodesStock: 'full' | 'partial' | 'none';
[other: string]: any;
};

View File

@ -2,8 +2,8 @@
"$schema": "http://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
@ -16,7 +16,7 @@
"noImplicitReturns": true,
"esModuleInterop": true,
"typeRoots": [
"node_modules/@types"
"./node_modules/@types"
],
"lib": [
"esnext",

View File

@ -14,7 +14,7 @@
"misskey-js": "workspace:*"
},
"devDependencies": {
"@typescript-eslint/parser": "6.4.1",
"@typescript-eslint/parser": "6.5.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
"eslint": "8.48.0",
"eslint-plugin-import": "2.28.1",

View File

@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": false,
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"removeComments": false,
"noLib": false,
"strict": true,
@ -24,8 +24,8 @@
"@/*": ["./src/*"]
},
"typeRoots": [
"node_modules/@types",
"@types"
"./node_modules/@types",
"./src/@types"
],
"lib": [
"esnext",

File diff suppressed because it is too large Load Diff