Merge remote-tracking branch 'msky/develop' into fix-freebsd
This commit is contained in:
commit
ab74696488
|
@ -220,5 +220,10 @@ allowedPrivateNetworks: [
|
||||||
'127.0.0.1/32'
|
'127.0.0.1/32'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Disable automatic redirect for ActivityPub object lookup. (default: false)
|
||||||
|
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
|
||||||
|
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
|
||||||
|
#disallowExternalApRedirect: true
|
||||||
|
|
||||||
# Upload or download file size limits (bytes)
|
# Upload or download file size limits (bytes)
|
||||||
#maxFileSize: 262144000
|
#maxFileSize: 262144000
|
||||||
|
|
|
@ -235,6 +235,11 @@ signToActivityPubGet: true
|
||||||
# '127.0.0.1/32'
|
# '127.0.0.1/32'
|
||||||
#]
|
#]
|
||||||
|
|
||||||
|
# Disable automatic redirect for ActivityPub object lookup. (default: false)
|
||||||
|
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
|
||||||
|
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
|
||||||
|
#disallowExternalApRedirect: true
|
||||||
|
|
||||||
# Upload or download file size limits (bytes)
|
# Upload or download file size limits (bytes)
|
||||||
#maxFileSize: 262144000
|
#maxFileSize: 262144000
|
||||||
|
|
||||||
|
|
|
@ -334,6 +334,11 @@ signToActivityPubGet: true
|
||||||
# '127.0.0.1/32'
|
# '127.0.0.1/32'
|
||||||
#]
|
#]
|
||||||
|
|
||||||
|
# Disable automatic redirect for ActivityPub object lookup. (default: false)
|
||||||
|
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
|
||||||
|
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
|
||||||
|
#disallowExternalApRedirect: true
|
||||||
|
|
||||||
# Upload or download file size limits (bytes)
|
# Upload or download file size limits (bytes)
|
||||||
#maxFileSize: 262144000
|
#maxFileSize: 262144000
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- name: Restore eslint cache
|
- name: Restore eslint cache
|
||||||
uses: actions/cache@v4.2.0
|
uses: actions/cache@v4.2.1
|
||||||
with:
|
with:
|
||||||
path: ${{ env.eslint-cache-path }}
|
path: ${{ env.eslint-cache-path }}
|
||||||
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
## Unreleased
|
## 2025.2.1
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Feat: アクセストークン発行時に通知するように
|
- Feat: アクセストークン発行時に通知するように
|
||||||
|
@ -12,12 +12,15 @@
|
||||||
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
|
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
|
||||||
- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
|
- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
|
||||||
- Enhance: リアクションする際に確認ダイアログを表示できるように
|
- Enhance: リアクションする際に確認ダイアログを表示できるように
|
||||||
|
- Enhance: CWの注釈で入力済みの文字数を表示
|
||||||
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
|
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
|
||||||
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
|
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
|
||||||
- Fix: Play の再読込時に UI が以前の状態を引き継いでしまう問題を修正 `#14378`
|
- Fix: Play の再読込時に UI が以前の状態を引き継いでしまう問題を修正 `#14378`
|
||||||
- Fix: カスタム絵文字管理画面(beta)にてisSensitive/localOnlyの絞り込みが上手くいかない問題の修正 ( #15445 )
|
- Fix: カスタム絵文字管理画面(beta)にてisSensitive/localOnlyの絞り込みが上手くいかない問題の修正 ( #15445 )
|
||||||
|
- Fix: CWの注釈が100文字を超えている場合、ノート投稿ボタンを非アクティブに
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- Enhance: 成り済まし対策として、ActivityPub照会された時にリモートのリダイレクトを拒否できるように (config.disallowExternalApRedirect)
|
||||||
- Fix: `following/invalidate`でフォロワーを解除しようとしているユーザーの情報を返すように
|
- Fix: `following/invalidate`でフォロワーを解除しようとしているユーザーの情報を返すように
|
||||||
- Fix: オブジェクトストレージの設定でPrefixを設定していなかった場合nullまたは空文字になる問題を修正
|
- Fix: オブジェクトストレージの設定でPrefixを設定していなかった場合nullまたは空文字になる問題を修正
|
||||||
- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
|
- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
|
||||||
|
|
|
@ -10896,13 +10896,7 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"title": string;
|
"title": string;
|
||||||
/**
|
/**
|
||||||
* このサーバーと通信することはできましたが、得られたデータが不正なものでした。
|
* このサーバーと通信することはできましたが、得られたデータが不正なものでした。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。
|
||||||
*/
|
|
||||||
"description": string;
|
|
||||||
};
|
|
||||||
"_responseInvalidIdHostNotMatch": {
|
|
||||||
/**
|
|
||||||
* 入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。
|
|
||||||
*/
|
*/
|
||||||
"description": string;
|
"description": string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2911,9 +2911,7 @@ _remoteLookupErrors:
|
||||||
description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。"
|
description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。"
|
||||||
_responseInvalid:
|
_responseInvalid:
|
||||||
title: "レスポンスが不正です"
|
title: "レスポンスが不正です"
|
||||||
description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。"
|
description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
|
||||||
_responseInvalidIdHostNotMatch:
|
|
||||||
description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
|
|
||||||
_noSuchObject:
|
_noSuchObject:
|
||||||
title: "見つかりません"
|
title: "見つかりません"
|
||||||
description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。"
|
description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.2.0",
|
"version": "2025.2.1-beta.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"bullmq": "5.41.0",
|
"bullmq": "5.41.1",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"canvas": "3.1.0",
|
"canvas": "3.1.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "9.0.2",
|
||||||
|
|
|
@ -73,6 +73,7 @@ type Source = {
|
||||||
proxyBypassHosts?: string[];
|
proxyBypassHosts?: string[];
|
||||||
|
|
||||||
allowedPrivateNetworks?: string[];
|
allowedPrivateNetworks?: string[];
|
||||||
|
disallowExternalApRedirect?: boolean;
|
||||||
|
|
||||||
maxFileSize?: number;
|
maxFileSize?: number;
|
||||||
|
|
||||||
|
@ -149,6 +150,7 @@ export type Config = {
|
||||||
proxySmtp: string | undefined;
|
proxySmtp: string | undefined;
|
||||||
proxyBypassHosts: string[] | undefined;
|
proxyBypassHosts: string[] | undefined;
|
||||||
allowedPrivateNetworks: string[] | undefined;
|
allowedPrivateNetworks: string[] | undefined;
|
||||||
|
disallowExternalApRedirect: boolean;
|
||||||
maxFileSize: number;
|
maxFileSize: number;
|
||||||
clusterLimit: number | undefined;
|
clusterLimit: number | undefined;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -287,6 +289,7 @@ export function loadConfig(): Config {
|
||||||
proxySmtp: config.proxySmtp,
|
proxySmtp: config.proxySmtp,
|
||||||
proxyBypassHosts: config.proxyBypassHosts,
|
proxyBypassHosts: config.proxyBypassHosts,
|
||||||
allowedPrivateNetworks: config.allowedPrivateNetworks,
|
allowedPrivateNetworks: config.allowedPrivateNetworks,
|
||||||
|
disallowExternalApRedirect: config.disallowExternalApRedirect ?? false,
|
||||||
maxFileSize: config.maxFileSize ?? 262144000,
|
maxFileSize: config.maxFileSize ?? 262144000,
|
||||||
clusterLimit: config.clusterLimit,
|
clusterLimit: config.clusterLimit,
|
||||||
outgoingAddress: config.outgoingAddress,
|
outgoingAddress: config.outgoingAddress,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { Config } from '@/config.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||||
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
|
import { assertActivityMatchesUrls, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
import type { IObject } from '@/core/activitypub/type.js';
|
import type { IObject } from '@/core/activitypub/type.js';
|
||||||
import type { Response } from 'node-fetch';
|
import type { Response } from 'node-fetch';
|
||||||
import type { URL } from 'node:url';
|
import type { URL } from 'node:url';
|
||||||
|
@ -215,7 +215,7 @@ export class HttpRequestService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObject> {
|
public async getActivityJson(url: string, isLocalAddressAllowed = false, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
|
||||||
const res = await this.send(url, {
|
const res = await this.send(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -232,7 +232,7 @@ export class HttpRequestService {
|
||||||
const finalUrl = res.url; // redirects may have been involved
|
const finalUrl = res.url; // redirects may have been involved
|
||||||
const activity = await res.json() as IObject;
|
const activity = await res.json() as IObject;
|
||||||
|
|
||||||
assertActivityMatchesUrls(activity, [finalUrl]);
|
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
|
||||||
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||||
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
|
import { assertActivityMatchesUrls, FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
import type { IObject } from './type.js';
|
import type { IObject } from './type.js';
|
||||||
|
|
||||||
type Request = {
|
type Request = {
|
||||||
|
@ -185,7 +185,7 @@ export class ApRequestService {
|
||||||
* @param url URL to fetch
|
* @param url URL to fetch
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<unknown> {
|
public async signedGet(url: string, user: { id: MiUser['id'] }, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict, followAlternate?: boolean): Promise<unknown> {
|
||||||
const _followAlternate = followAlternate ?? true;
|
const _followAlternate = followAlternate ?? true;
|
||||||
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ export class ApRequestService {
|
||||||
if (alternate) {
|
if (alternate) {
|
||||||
const href = alternate.getAttribute('href');
|
const href = alternate.getAttribute('href');
|
||||||
if (href && this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) {
|
if (href && this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) {
|
||||||
return await this.signedGet(href, user, false);
|
return await this.signedGet(href, user, allowSoftfail, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -258,7 +258,7 @@ export class ApRequestService {
|
||||||
const finalUrl = res.url; // redirects may have been involved
|
const finalUrl = res.url; // redirects may have been involved
|
||||||
const activity = await res.json() as IObject;
|
const activity = await res.json() as IObject;
|
||||||
|
|
||||||
assertActivityMatchesUrls(activity, [finalUrl]);
|
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
|
||||||
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
|
@ -72,7 +73,7 @@ export class Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async resolve(value: string | IObject): Promise<IObject> {
|
public async resolve(value: string | IObject, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -108,8 +109,8 @@ export class Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
const object = (this.user
|
const object = (this.user
|
||||||
? await this.apRequestService.signedGet(value, this.user) as IObject
|
? await this.apRequestService.signedGet(value, this.user, allowSoftfail) as IObject
|
||||||
: await this.httpRequestService.getActivityJson(value)) as IObject;
|
: await this.httpRequestService.getActivityJson(value, undefined, allowSoftfail)) as IObject;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Array.isArray(object['@context']) ?
|
Array.isArray(object['@context']) ?
|
||||||
|
@ -119,18 +120,6 @@ export class Resolver {
|
||||||
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpRequestService / ApRequestService have already checked that
|
|
||||||
// `object.id` or `object.url` matches the URL used to fetch the
|
|
||||||
// object after redirects; here we double-check that no redirects
|
|
||||||
// bounced between hosts
|
|
||||||
if (object.id == null) {
|
|
||||||
throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', 'invalid AP object: missing id');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
|
|
||||||
throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,124 @@
|
||||||
*/
|
*/
|
||||||
import type { IObject } from '../type.js';
|
import type { IObject } from '../type.js';
|
||||||
|
|
||||||
export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
|
export enum FetchAllowSoftFailMask {
|
||||||
const hosts = urls.map(it => new URL(it).host);
|
// Allow no softfail flags
|
||||||
|
Strict = 0,
|
||||||
const idOk = activity.id !== undefined && hosts.includes(new URL(activity.id).host);
|
// The values in tuple (requestUrl, finalUrl, objectId) are not all identical
|
||||||
|
//
|
||||||
// technically `activity.url` could be an `ApObject = IObject |
|
// This condition is common for user-initiated lookups but should not be allowed in federation loop
|
||||||
// string | (IObject | string)[]`, but if it's a complicated thing
|
//
|
||||||
// and the `activity.id` doesn't match, I think we're fine
|
// Allow variations:
|
||||||
// rejecting the activity
|
// good example: https://alice.example.com/@user -> https://alice.example.com/user/:userId
|
||||||
const urlOk = typeof(activity.url) === 'string' && hosts.includes(new URL(activity.url).host);
|
// problematic example: https://alice.example.com/redirect?url=https://bad.example.com/ -> https://bad.example.com/ -> https://alice.example.com/somethingElse
|
||||||
|
NonCanonicalId = 1 << 0,
|
||||||
if (!idOk && !urlOk) {
|
// Allow the final object to be at most one subdomain deeper than the request URL, similar to SPF relaxed alignment
|
||||||
throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${activity?.url}) match location(${urls})`);
|
//
|
||||||
}
|
// Currently no code path allows this flag to be set, but is kept in case of future use as some niche deployments do this, and we provide a pre-reviewed mechanism to opt-in.
|
||||||
|
//
|
||||||
|
// Allow variations:
|
||||||
|
// good example: https://example.com/@user -> https://activitypub.example.com/@user { id: 'https://activitypub.example.com/@user' }
|
||||||
|
// problematic example: https://example.com/@user -> https://untrusted.example.com/@user { id: 'https://untrusted.example.com/@user' }
|
||||||
|
MisalignedOrigin = 1 << 1,
|
||||||
|
// The requested URL has a different host than the returned object ID, although the final URL is still consistent with the object ID
|
||||||
|
//
|
||||||
|
// This condition is common for user-initiated lookups using an intermediate host but should not be allowed in federation loops
|
||||||
|
//
|
||||||
|
// Allow variations:
|
||||||
|
// good example: https://alice.example.com/@user@bob.example.com -> https://bob.example.com/@user { id: 'https://bob.example.com/@user' }
|
||||||
|
// problematic example: https://alice.example.com/definitelyAlice -> https://bob.example.com/@somebodyElse { id: 'https://bob.example.com/@somebodyElse' }
|
||||||
|
CrossOrigin = 1 << 2 | MisalignedOrigin,
|
||||||
|
// Allow all softfail flags
|
||||||
|
//
|
||||||
|
// do not use this flag on released code
|
||||||
|
Any = ~0,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fuzz match on whether the candidate host has authority over the request host
|
||||||
|
*
|
||||||
|
* @param requestHost The host of the requested resources
|
||||||
|
* @param candidateHost The host of final response
|
||||||
|
* @returns Whether the candidate host has authority over the request host, or if a soft fail is required for a match
|
||||||
|
*/
|
||||||
|
function hostFuzzyMatch(requestHost: string, candidateHost: string): FetchAllowSoftFailMask {
|
||||||
|
const requestFqdn = requestHost.endsWith('.') ? requestHost : `${requestHost}.`;
|
||||||
|
const candidateFqdn = candidateHost.endsWith('.') ? candidateHost : `${candidateHost}.`;
|
||||||
|
|
||||||
|
if (requestFqdn === candidateFqdn) {
|
||||||
|
return FetchAllowSoftFailMask.Strict;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow only one case where candidateHost is a first-level subdomain of requestHost
|
||||||
|
const requestDnsDepth = requestFqdn.split('.').length;
|
||||||
|
const candidateDnsDepth = candidateFqdn.split('.').length;
|
||||||
|
|
||||||
|
if ((candidateDnsDepth - requestDnsDepth) !== 1) {
|
||||||
|
return FetchAllowSoftFailMask.CrossOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (`.${candidateHost}`.endsWith(`.${requestHost}`)) {
|
||||||
|
return FetchAllowSoftFailMask.MisalignedOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FetchAllowSoftFailMask.CrossOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize host names by removing www. prefix
|
||||||
|
function normalizeSynonymousSubdomain(url: URL | string): URL {
|
||||||
|
const urlParsed = url instanceof URL ? url : new URL(url);
|
||||||
|
const host = urlParsed.host;
|
||||||
|
const normalizedHost = host.replace(/^www\./, '');
|
||||||
|
return new URL(urlParsed.toString().replace(host, normalizedHost));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertActivityMatchesUrls(requestUrl: string | URL, activity: IObject, candidateUrls: (string | URL)[], allowSoftfail: FetchAllowSoftFailMask): FetchAllowSoftFailMask {
|
||||||
|
// must have a unique identifier to verify authority
|
||||||
|
if (!activity.id) {
|
||||||
|
throw new Error('bad Activity: missing id field');
|
||||||
|
}
|
||||||
|
|
||||||
|
let softfail = 0;
|
||||||
|
|
||||||
|
// if the flag is allowed, set the flag on return otherwise throw
|
||||||
|
const requireSoftfail = (needed: FetchAllowSoftFailMask, message: string) => {
|
||||||
|
if ((allowSoftfail & needed) !== needed) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
softfail |= needed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestUrlParsed = normalizeSynonymousSubdomain(requestUrl);
|
||||||
|
const idParsed = normalizeSynonymousSubdomain(activity.id);
|
||||||
|
|
||||||
|
const candidateUrlsParsed = candidateUrls.map(it => normalizeSynonymousSubdomain(it));
|
||||||
|
|
||||||
|
const requestUrlSecure = requestUrlParsed.protocol === 'https:';
|
||||||
|
const finalUrlSecure = candidateUrlsParsed.every(it => it.protocol === 'https:');
|
||||||
|
if (requestUrlSecure && !finalUrlSecure) {
|
||||||
|
throw new Error(`bad Activity: id(${activity.id}) is not allowed to have http:// in the url`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare final URL to the ID
|
||||||
|
if (!candidateUrlsParsed.some(it => it.href === idParsed.href)) {
|
||||||
|
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match response url(${candidateUrlsParsed.map(it => it.toString())})`);
|
||||||
|
|
||||||
|
// at lease host need to match exactly (ActivityPub requirement)
|
||||||
|
if (!candidateUrlsParsed.some(it => idParsed.host === it.host)) {
|
||||||
|
throw new Error(`bad Activity: id(${activity.id}) does not match response host(${candidateUrlsParsed.map(it => it.host)})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare request URL to the ID
|
||||||
|
if (!requestUrlParsed.href.includes(idParsed.href)) {
|
||||||
|
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match request url(${requestUrlParsed.toString()})`);
|
||||||
|
|
||||||
|
// if cross-origin lookup is allowed, we can accept some variation between the original request URL to the final object ID (but not between the final URL and the object ID)
|
||||||
|
const hostResult = hostFuzzyMatch(requestUrlParsed.host, idParsed.host);
|
||||||
|
|
||||||
|
requireSoftfail(hostResult, `bad Activity: id(${activity.id}) is valid but is not the same origin as request url(${requestUrlParsed.toString()})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return softfail;
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,12 +107,12 @@ export class InboxProcessorService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// それでもわからなければ終了
|
// それでもわからなければ終了
|
||||||
if (authUser == null) {
|
if (authUser == null) {
|
||||||
throw new Bull.UnrecoverableError('skip: failed to resolve user');
|
throw new Bull.UnrecoverableError(`skip: failed to resolve user ${getApId(activity.actor)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// publicKey がなくても終了
|
// publicKey がなくても終了
|
||||||
if (authUser.key == null) {
|
if (authUser.key == null) {
|
||||||
throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
|
throw new Bull.UnrecoverableError(`skip: failed to resolve user publicKey ${getApId(activity.actor)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
|
|
|
@ -103,6 +103,43 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
serve: false,
|
serve: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// if the requester looks like to be performing an ActivityPub object lookup, reject all external redirects
|
||||||
|
//
|
||||||
|
// this will break lookup that involve copying a URL from a third-party server, like trying to lookup http://charlie.example.com/@alice@alice.com
|
||||||
|
//
|
||||||
|
// this is not required by standard but protect us from peers that did not validate final URL.
|
||||||
|
if (this.config.disallowExternalApRedirect) {
|
||||||
|
const maybeApLookupRegex = /application\/activity\+json|application\/ld\+json.+activitystreams/i;
|
||||||
|
fastify.addHook('onSend', (request, reply, _, done) => {
|
||||||
|
const location = reply.getHeader('location');
|
||||||
|
if (reply.statusCode < 300 || reply.statusCode >= 400 || typeof location !== 'string') {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!maybeApLookupRegex.test(request.headers.accept ?? '')) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveLocation = process.env.NODE_ENV === 'production' ? location : location.replace(/^http:\/\//, 'https://');
|
||||||
|
if (effectiveLocation.startsWith(`https://${this.config.host}/`)) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.status(406);
|
||||||
|
reply.removeHeader('location');
|
||||||
|
reply.header('content-type', 'text/plain; charset=utf-8');
|
||||||
|
reply.header('link', `<${encodeURI(location)}>; rel="canonical"`);
|
||||||
|
done(null, [
|
||||||
|
"Refusing to relay remote ActivityPub object lookup.",
|
||||||
|
"",
|
||||||
|
`Please remove 'application/activity+json' and 'application/ld+json' from the Accept header or fetch using the authoritative URL at ${location}.`,
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
|
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
|
||||||
fastify.register(this.openApiServerService.createServer);
|
fastify.register(this.openApiServerService.createServer);
|
||||||
fastify.register(this.fileServerService.createServer);
|
fastify.register(this.fileServerService.createServer);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
@ -53,11 +54,6 @@ export const meta = {
|
||||||
code: 'RESPONSE_INVALID',
|
code: 'RESPONSE_INVALID',
|
||||||
id: '70193c39-54f3-4813-82f0-70a680f7495b',
|
id: '70193c39-54f3-4813-82f0-70a680f7495b',
|
||||||
},
|
},
|
||||||
responseInvalidIdHostNotMatch: {
|
|
||||||
message: 'Requested URI and response URI host does not match.',
|
|
||||||
code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
|
|
||||||
id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
|
|
||||||
},
|
|
||||||
noSuchObject: {
|
noSuchObject: {
|
||||||
message: 'No such object.',
|
message: 'No such object.',
|
||||||
code: 'NO_SUCH_OBJECT',
|
code: 'NO_SUCH_OBJECT',
|
||||||
|
@ -153,7 +149,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
// リモートから一旦オブジェクトフェッチ
|
// リモートから一旦オブジェクトフェッチ
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
const object = await resolver.resolve(uri).catch((err) => {
|
// allow ap/show exclusively to lookup URLs that are cross-origin or non-canonical (like https://alice.example.com/@bob@bob.example.com -> https://bob.example.com/@bob)
|
||||||
|
const object = await resolver.resolve(uri, FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId).catch((err) => {
|
||||||
if (err instanceof IdentifiableError) {
|
if (err instanceof IdentifiableError) {
|
||||||
switch (err.id) {
|
switch (err.id) {
|
||||||
// resolve
|
// resolve
|
||||||
|
@ -165,10 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
|
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
|
||||||
throw new ApiError(meta.errors.federationNotAllowed);
|
throw new ApiError(meta.errors.federationNotAllowed);
|
||||||
case '72180409-793c-4973-868e-5a118eb5519b':
|
case '72180409-793c-4973-868e-5a118eb5519b':
|
||||||
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
|
|
||||||
throw new ApiError(meta.errors.responseInvalid);
|
throw new ApiError(meta.errors.responseInvalid);
|
||||||
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
|
|
||||||
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
|
|
||||||
|
|
||||||
// resolveLocal
|
// resolveLocal
|
||||||
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
|
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
|
||||||
|
|
|
@ -5,112 +5,107 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
|
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#misskey_app,
|
#misskey_app,
|
||||||
#splash {
|
#splash {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
html {
|
html {
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
color: #dfddcc;
|
color: #dfddcc;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 0px 12px 0px 12px;
|
padding: 0px 12px 0px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-big {
|
.button-big {
|
||||||
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
|
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-big:hover {
|
.button-big:hover {
|
||||||
background: rgb(153, 204, 0);
|
background: rgb(153, 204, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-small {
|
.button-small {
|
||||||
background: #444;
|
background: #444;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-small:hover {
|
.button-small:hover {
|
||||||
background: #555;
|
background: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-label-big {
|
.button-label-big {
|
||||||
color: #222;
|
color: #222;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 1.2em;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-label-small {
|
.button-label-small {
|
||||||
color: rgb(153, 204, 0);
|
color: rgb(153, 204, 0);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: rgb(134, 179, 0);
|
color: rgb(134, 179, 0);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
li {
|
li {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
|
||||||
|
|
||||||
.dont-worry,
|
|
||||||
#msg {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-warning {
|
.icon-warning {
|
||||||
color: #dec340;
|
color: #dec340;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 32px;
|
font-size: 1.5em;
|
||||||
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
display: block;
|
display: block;
|
||||||
font-family: Fira, FiraCode, monospace;
|
font-family: Fira, FiraCode, monospace;
|
||||||
background: #333;
|
background: #333;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary {
|
#errorInfo summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary > * {
|
#errorInfo summary>* {
|
||||||
display: inline;
|
display: inline;
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
details {
|
#errorInfo {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const locale = JSON.parse(localStorage.getItem('locale') || '{}');
|
||||||
|
|
||||||
|
const messages = Object.assign({
|
||||||
|
title: 'Failed to initialize Misskey',
|
||||||
|
serverError: 'If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.',
|
||||||
|
solution: 'The following actions may solve the problem.',
|
||||||
|
solution1: 'Update your os and browser',
|
||||||
|
solution2: 'Disable an adblocker',
|
||||||
|
solution3: 'Clear the browser cache',
|
||||||
|
solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
|
||||||
|
otherOption: 'Other options',
|
||||||
|
otherOption1: 'Clear preferences and cache',
|
||||||
|
otherOption2: 'Start the simple client',
|
||||||
|
otherOption3: 'Start the repair tool',
|
||||||
|
}, locale?._bootErrors || {});
|
||||||
|
const reload = locale?.reload || 'Reload';
|
||||||
|
|
||||||
|
const reloadEls = document.querySelectorAll('[data-i18n-reload]');
|
||||||
|
for (const el of reloadEls) {
|
||||||
|
el.textContent = reload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18nEls = document.querySelectorAll('[data-i18n]');
|
||||||
|
for (const el of i18nEls) {
|
||||||
|
const key = el.dataset.i18n;
|
||||||
|
if (key && messages[key]) {
|
||||||
|
el.textContent = messages[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
|
@ -6,7 +6,7 @@ doctype html
|
||||||
| |_|___ ___| |_ ___ _ _
|
| |_|___ ___| |_ ___ _ _
|
||||||
| | | | |_ -|_ -| '_| -_| | |
|
| | | | |_ -|_ -| '_| -_| | |
|
||||||
|_|_|_|_|___|___|_,_|___|_ |
|
|_|_|_|_|___|___|_,_|___|_ |
|
||||||
|___|
|
|___|
|
||||||
Thank you for using Misskey!
|
Thank you for using Misskey!
|
||||||
If you are reading this message... how about joining the development?
|
If you are reading this message... how about joining the development?
|
||||||
https://github.com/misskey-dev/misskey
|
https://github.com/misskey-dev/misskey
|
||||||
|
@ -27,39 +27,45 @@ html
|
||||||
style
|
style
|
||||||
include ../error.css
|
include ../error.css
|
||||||
|
|
||||||
|
script
|
||||||
|
include ../error.js
|
||||||
|
|
||||||
body
|
body
|
||||||
svg.icon-warning(xmlns="http://www.w3.org/2000/svg", viewBox="0 0 24 24", stroke-width="2", stroke="currentColor", fill="none", stroke-linecap="round", stroke-linejoin="round")
|
svg.icon-warning(xmlns="http://www.w3.org/2000/svg", viewBox="0 0 24 24", stroke-width="2", stroke="currentColor", fill="none", stroke-linecap="round", stroke-linejoin="round")
|
||||||
path(stroke="none", d="M0 0h24v24H0z", fill="none")
|
path(stroke="none", d="M0 0h24v24H0z", fill="none")
|
||||||
path(d="M12 9v2m0 4v.01")
|
path(d="M12 9v2m0 4v.01")
|
||||||
path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75")
|
path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75")
|
||||||
|
|
||||||
h1 An error has occurred!
|
h1(data-i18n="title") Failed to initialize Misskey
|
||||||
|
|
||||||
button.button-big(onclick="location.reload();")
|
button.button-big(onclick="location.reload();")
|
||||||
span.button-label-big Refresh
|
span.button-label-big(data-i18n-reload) Reload
|
||||||
|
|
||||||
p.dont-worry Don't worry, it's (probably) not your fault.
|
p(data-i18n="serverError") If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
|
||||||
|
|
||||||
p If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
|
|
||||||
|
|
||||||
div#errors
|
div#errors
|
||||||
code.
|
code.
|
||||||
ERROR CODE: #{code}
|
ERROR CODE: #{code}
|
||||||
ERROR ID: #{id}
|
ERROR ID: #{id}
|
||||||
|
|
||||||
p You may also try the following options:
|
p
|
||||||
|
b(data-i18n="solution") The following actions may solve the problem.
|
||||||
|
|
||||||
p Update your os and browser.
|
p(data-i18n="solution1") Update your os and browser
|
||||||
p Disable an adblocker.
|
p(data-i18n="solution2") Disable an adblocker
|
||||||
|
p(data-i18n="solution3") Clear your browser cache
|
||||||
|
p(data-i18n="solution4") (Tor Browser) Set dom.webaudio.enabled to true
|
||||||
|
|
||||||
a(href="/flush")
|
details(style="color: #86b300;")
|
||||||
button.button-small
|
summary(data-i18n="otherOption") Other options
|
||||||
span.button-label-small Clear preferences and cache
|
a(href="/flush")
|
||||||
br
|
button.button-small
|
||||||
a(href="/cli")
|
span.button-label-small(data-i18n="otherOption1") Clear preferences and cache
|
||||||
button.button-small
|
br
|
||||||
span.button-label-small Start the simple client
|
a(href="/cli")
|
||||||
br
|
button.button-small
|
||||||
a(href="/bios")
|
span.button-label-small(data-i18n="otherOption2") Start the simple client
|
||||||
button.button-small
|
br
|
||||||
span.button-label-small Start the repair tool
|
a(href="/bios")
|
||||||
|
button.button-small
|
||||||
|
span.button-label-small(data-i18n="otherOption3") Start the repair tool
|
||||||
|
|
|
@ -397,7 +397,7 @@ describe('Timelines', () => {
|
||||||
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false);
|
||||||
assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false);
|
||||||
}, 1000 * 15);
|
}, 1000 * 30);
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
|
test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
|
@ -8,6 +8,8 @@ import httpSignature from '@peertube/http-signature';
|
||||||
|
|
||||||
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||||
|
import { assertActivityMatchesUrls, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
|
import { IObject } from '@/core/activitypub/type.js';
|
||||||
|
|
||||||
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
||||||
return {
|
return {
|
||||||
|
@ -24,6 +26,10 @@ export const buildParsedSignature = (signingString: string, signature: string, a
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function cartesianProduct<T, U>(a: T[], b: U[]): [T, U][] {
|
||||||
|
return a.flatMap(a => b.map(b => [a, b] as [T, U]));
|
||||||
|
}
|
||||||
|
|
||||||
describe('ap-request', () => {
|
describe('ap-request', () => {
|
||||||
test('createSignedPost with verify', async () => {
|
test('createSignedPost with verify', async () => {
|
||||||
const keypair = await genRsaKeyPair();
|
const keypair = await genRsaKeyPair();
|
||||||
|
@ -58,4 +64,123 @@ describe('ap-request', () => {
|
||||||
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
||||||
assert.deepStrictEqual(result, true);
|
assert.deepStrictEqual(result, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('rejects non matching domain', () => {
|
||||||
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Strict,
|
||||||
|
), 'validation should pass base case');
|
||||||
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Any,
|
||||||
|
), 'validation should fail no matter what if the response URL is inconsistent with the object ID');
|
||||||
|
|
||||||
|
// fix issues like threads
|
||||||
|
// https://github.com/misskey-dev/misskey/issues/15039
|
||||||
|
const withOrWithoutWWW = [
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
'https://www.alice.example.com/abc',
|
||||||
|
];
|
||||||
|
|
||||||
|
cartesianProduct(
|
||||||
|
cartesianProduct(
|
||||||
|
withOrWithoutWWW,
|
||||||
|
withOrWithoutWWW,
|
||||||
|
),
|
||||||
|
withOrWithoutWWW,
|
||||||
|
).forEach(([[a, b], c]) => {
|
||||||
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
|
a,
|
||||||
|
{ id: b } as IObject,
|
||||||
|
[
|
||||||
|
c,
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Strict,
|
||||||
|
), 'validation should pass with or without www. subdomain');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cross origin lookup', () => {
|
||||||
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://bob.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
|
), 'validation should pass if the response is otherwise consistent and cross-origin is allowed');
|
||||||
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://bob.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Strict,
|
||||||
|
), 'validation should fail if the response is otherwise consistent and cross-origin is not allowed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects non-canonical ID', () => {
|
||||||
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/@alice',
|
||||||
|
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
||||||
|
[
|
||||||
|
'https://alice.example.com/users/alice'
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Strict,
|
||||||
|
), 'throws if the response ID did not exactly match the expected ID');
|
||||||
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/@alice',
|
||||||
|
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
||||||
|
[
|
||||||
|
'https://alice.example.com/users/alice',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
|
), 'does not throw if non-canonical ID is allowed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('origin relaxed alignment', () => {
|
||||||
|
assert.doesNotThrow(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://ap.alice.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
|
), 'validation should pass if response is a subdomain of the expected origin');
|
||||||
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.multi-tenant.example.com/abc',
|
||||||
|
{ id: 'https://alice.multi-tenant.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://bob.multi-tenant.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
||||||
|
), 'validation should fail if response is a disjoint domain of the expected origin');
|
||||||
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'https://ap.alice.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Strict,
|
||||||
|
), 'throws if relaxed origin is forbidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resist HTTP downgrade', () => {
|
||||||
|
assert.throws(() => assertActivityMatchesUrls(
|
||||||
|
'https://alice.example.com/abc',
|
||||||
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
||||||
|
[
|
||||||
|
'http://alice.example.com/abc',
|
||||||
|
],
|
||||||
|
FetchAllowSoftFailMask.Strict,
|
||||||
|
), 'throws if HTTP downgrade is detected');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,16 +25,16 @@
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"frontend-shared": "workspace:*",
|
"frontend-shared": "workspace:*",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.34.7",
|
"rollup": "4.34.8",
|
||||||
"sass": "1.85.0",
|
"sass": "1.85.0",
|
||||||
"shiki": "2.4.1",
|
"shiki": "3.0.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"uuid": "11.0.5",
|
"uuid": "11.1.0",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"vite": "6.1.0",
|
"vite": "6.1.1",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -42,29 +42,29 @@
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.5.14",
|
"@types/ws": "8.5.14",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"@vitest/coverage-v8": "3.0.5",
|
"@vitest/coverage-v8": "3.0.6",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.32.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
"happy-dom": "17.1.0",
|
"happy-dom": "17.1.4",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.7.0",
|
"msw": "2.7.1",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"prettier": "3.5.1",
|
"prettier": "3.5.2",
|
||||||
"start-server-and-test": "2.0.10",
|
"start-server-and-test": "2.0.10",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "2.2.2",
|
"vue-component-type-helpers": "2.2.4",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.2.2"
|
"vue-tsc": "2.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ export default [
|
||||||
// TODO: Error while loading rule '@typescript-eslint/naming-convention': Cannot use 'in' operator to search for 'type' in undefined のため一時的に無効化
|
// TODO: Error while loading rule '@typescript-eslint/naming-convention': Cannot use 'in' operator to search for 'type' in undefined のため一時的に無効化
|
||||||
// See https://github.com/misskey-dev/misskey/pull/15311
|
// See https://github.com/misskey-dev/misskey/pull/15311
|
||||||
'js/i18n.ts',
|
'js/i18n.ts',
|
||||||
|
'js-built/',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -134,7 +134,6 @@ export function scrollToBottom(
|
||||||
|
|
||||||
export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
|
export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
|
||||||
const scrollTop = getScrollPosition(el);
|
const scrollTop = getScrollPosition(el);
|
||||||
if (_DEV_) console.log(scrollTop, tolerance, scrollTop <= tolerance);
|
|
||||||
return scrollTop <= tolerance;
|
return scrollTop <= tolerance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"esbuild": "0.25.0",
|
"esbuild": "0.25.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.32.0",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.0.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"canvas-confetti": "1.9.3",
|
"canvas-confetti": "1.9.3",
|
||||||
"chart.js": "4.4.7",
|
"chart.js": "4.4.8",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
|
@ -56,10 +56,10 @@
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.34.7",
|
"rollup": "4.34.8",
|
||||||
"sanitize-html": "2.14.0",
|
"sanitize-html": "2.14.0",
|
||||||
"sass": "1.85.0",
|
"sass": "1.85.0",
|
||||||
"shiki": "2.4.1",
|
"shiki": "3.0.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.173.0",
|
"three": "0.173.0",
|
||||||
|
@ -68,47 +68,47 @@
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"uuid": "11.0.5",
|
"uuid": "11.1.0",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "6.1.0",
|
"vite": "6.1.1",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.0",
|
"@misskey-dev/summaly": "5.2.0",
|
||||||
"@storybook/addon-actions": "8.5.6",
|
"@storybook/addon-actions": "8.5.8",
|
||||||
"@storybook/addon-essentials": "8.5.6",
|
"@storybook/addon-essentials": "8.5.8",
|
||||||
"@storybook/addon-interactions": "8.5.6",
|
"@storybook/addon-interactions": "8.5.8",
|
||||||
"@storybook/addon-links": "8.5.6",
|
"@storybook/addon-links": "8.5.8",
|
||||||
"@storybook/addon-mdx-gfm": "8.5.6",
|
"@storybook/addon-mdx-gfm": "8.5.8",
|
||||||
"@storybook/addon-storysource": "8.5.6",
|
"@storybook/addon-storysource": "8.5.8",
|
||||||
"@storybook/blocks": "8.5.6",
|
"@storybook/blocks": "8.5.8",
|
||||||
"@storybook/components": "8.5.6",
|
"@storybook/components": "8.5.8",
|
||||||
"@storybook/core-events": "8.5.6",
|
"@storybook/core-events": "8.5.8",
|
||||||
"@storybook/manager-api": "8.5.6",
|
"@storybook/manager-api": "8.5.8",
|
||||||
"@storybook/preview-api": "8.5.6",
|
"@storybook/preview-api": "8.5.8",
|
||||||
"@storybook/react": "8.5.6",
|
"@storybook/react": "8.5.8",
|
||||||
"@storybook/react-vite": "8.5.6",
|
"@storybook/react-vite": "8.5.8",
|
||||||
"@storybook/test": "8.5.6",
|
"@storybook/test": "8.5.8",
|
||||||
"@storybook/theming": "8.5.6",
|
"@storybook/theming": "8.5.8",
|
||||||
"@storybook/types": "8.5.6",
|
"@storybook/types": "8.5.8",
|
||||||
"@storybook/vue3": "8.5.6",
|
"@storybook/vue3": "8.5.8",
|
||||||
"@storybook/vue3-vite": "8.5.6",
|
"@storybook/vue3-vite": "8.5.8",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "1.9.0",
|
"@types/canvas-confetti": "1.9.0",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@types/matter-js": "0.19.8",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/sanitize-html": "2.13.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.5.14",
|
"@types/ws": "8.5.14",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"@vitest/coverage-v8": "3.0.5",
|
"@vitest/coverage-v8": "3.0.6",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
|
@ -116,24 +116,24 @@
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.32.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
"happy-dom": "17.1.0",
|
"happy-dom": "17.1.4",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.7.0",
|
"msw": "2.7.1",
|
||||||
"msw-storybook-addon": "2.0.4",
|
"msw-storybook-addon": "2.0.4",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"prettier": "3.5.1",
|
"prettier": "3.5.2",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.0.10",
|
"start-server-and-test": "2.0.10",
|
||||||
"storybook": "8.5.6",
|
"storybook": "8.5.8",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "3.0.5",
|
"vitest": "3.0.6",
|
||||||
"vitest-fetch-mock": "0.4.3",
|
"vitest-fetch-mock": "0.4.3",
|
||||||
"vue-component-type-helpers": "2.2.2",
|
"vue-component-type-helpers": "2.2.4",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.2.2"
|
"vue-tsc": "2.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||||
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
|
<div v-show="useCw" :class="$style.cwOuter">
|
||||||
|
<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
|
||||||
|
<div v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
|
||||||
|
</div>
|
||||||
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
||||||
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
||||||
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
||||||
|
@ -244,6 +247,12 @@ const maxTextLength = computed((): number => {
|
||||||
return instance ? instance.maxNoteTextLength : 1000;
|
return instance ? instance.maxNoteTextLength : 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cwTextLength = computed((): number => {
|
||||||
|
return cw.value?.length ?? 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxCwTextLength = 100;
|
||||||
|
|
||||||
const canPost = computed((): boolean => {
|
const canPost = computed((): boolean => {
|
||||||
return !props.mock && !posting.value && !posted.value &&
|
return !props.mock && !posting.value && !posted.value &&
|
||||||
(
|
(
|
||||||
|
@ -254,6 +263,7 @@ const canPost = computed((): boolean => {
|
||||||
quoteId.value != null
|
quoteId.value != null
|
||||||
) &&
|
) &&
|
||||||
(textLength.value <= maxTextLength.value) &&
|
(textLength.value <= maxTextLength.value) &&
|
||||||
|
(cwTextLength.value <= maxCwTextLength) &&
|
||||||
(files.value.length <= 16) &&
|
(files.value.length <= 16) &&
|
||||||
(!poll.value || poll.value.choices.length >= 2);
|
(!poll.value || poll.value.choices.length >= 2);
|
||||||
});
|
});
|
||||||
|
@ -1273,12 +1283,34 @@ html[data-color-scheme=light] .preview {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cwOuter {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.cw {
|
.cw {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cwTextCount {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 2px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: .9em;
|
||||||
|
color: var(--MI_THEME-warn);
|
||||||
|
border-radius: 6px;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 1.6em;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.cwTextOver {
|
||||||
|
color: #ff2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hashtags {
|
.hashtags {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
|
|
@ -15,9 +15,9 @@ export type MkABehavior = 'window' | 'browser' | null;
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, shallowRef } from 'vue';
|
import { computed, inject, shallowRef } from 'vue';
|
||||||
|
import { url } from '@@/js/config.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||||
import { url } from '@@/js/config.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
// NIRAX --- A lightweight router
|
// NIRAX --- A lightweight router
|
||||||
|
|
||||||
import { onMounted, shallowRef } from 'vue';
|
import { onMounted, shallowRef } from 'vue';
|
||||||
import type { Component, ShallowRef } from 'vue';
|
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import type { Component, ShallowRef } from 'vue';
|
||||||
|
|
||||||
function safeURIDecode(str: string): string {
|
function safeURIDecode(str: string): string {
|
||||||
try {
|
try {
|
||||||
|
@ -242,8 +242,6 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||||
hash,
|
hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_DEV_) console.log('Routing: ', path, queryString);
|
|
||||||
|
|
||||||
function check(routes: RouteDef[], _parts: string[]): Resolved | null {
|
function check(routes: RouteDef[], _parts: string[]): Resolved | null {
|
||||||
forEachRouteLoop:
|
forEachRouteLoop:
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
// PIZZAX --- A lightweight store
|
// PIZZAX --- A lightweight store
|
||||||
|
|
||||||
import { onUnmounted, ref, watch } from 'vue';
|
import { onUnmounted, ref, watch } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import { BroadcastChannel } from 'broadcast-channel';
|
import { BroadcastChannel } from 'broadcast-channel';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { get, set } from '@/scripts/idb-proxy.js';
|
import { get, set } from '@/scripts/idb-proxy.js';
|
||||||
|
@ -113,7 +113,6 @@ export class Storage<T extends StateDef> {
|
||||||
this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
|
this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
|
||||||
} else {
|
} else {
|
||||||
this.reactiveState[k].value = this.state[k] = v.default;
|
this.reactiveState[k].value = this.state[k] = v.default;
|
||||||
if (_DEV_) console.log('Use default value', k, v.default);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,12 +179,9 @@ export class Storage<T extends StateDef> {
|
||||||
// (JSON.parse(JSON.stringify(value))の代わり)
|
// (JSON.parse(JSON.stringify(value))の代わり)
|
||||||
const rawValue = deepClone(value);
|
const rawValue = deepClone(value);
|
||||||
|
|
||||||
if (_DEV_) console.log('set', key, rawValue, value);
|
|
||||||
|
|
||||||
this.reactiveState[key].value = this.state[key] = rawValue;
|
this.reactiveState[key].value = this.state[key] = rawValue;
|
||||||
|
|
||||||
return this.addIdbSetJob(async () => {
|
return this.addIdbSetJob(async () => {
|
||||||
if (_DEV_) console.log(`set ${String(key)} start`);
|
|
||||||
switch (this.def[key].where) {
|
switch (this.def[key].where) {
|
||||||
case 'device': {
|
case 'device': {
|
||||||
this.pizzaxChannel.postMessage({
|
this.pizzaxChannel.postMessage({
|
||||||
|
@ -224,7 +220,6 @@ export class Storage<T extends StateDef> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_DEV_) console.log(`set ${String(key)} complete`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,9 +242,9 @@ export class Storage<T extends StateDef> {
|
||||||
getter?: (v: T[K]['default']) => R,
|
getter?: (v: T[K]['default']) => R,
|
||||||
setter?: (v: R) => T[K]['default'],
|
setter?: (v: R) => T[K]['default'],
|
||||||
): {
|
): {
|
||||||
get: () => R;
|
get: () => R;
|
||||||
set: (value: R) => void;
|
set: (value: R) => void;
|
||||||
} {
|
} {
|
||||||
const valueRef = ref(this.state[key]);
|
const valueRef = ref(this.state[key]);
|
||||||
|
|
||||||
const stop = watch(this.reactiveState[key], val => {
|
const stop = watch(this.reactiveState[key], val => {
|
||||||
|
|
|
@ -54,10 +54,6 @@ export async function lookup(router?: Router) {
|
||||||
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
|
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
|
||||||
text = i18n.ts._remoteLookupErrors._responseInvalid.description;
|
text = i18n.ts._remoteLookupErrors._responseInvalid.description;
|
||||||
break;
|
break;
|
||||||
case 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a':
|
|
||||||
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
|
|
||||||
text = i18n.ts._remoteLookupErrors._responseInvalidIdHostNotMatch.description;
|
|
||||||
break;
|
|
||||||
case 'dc94d745-1262-4e63-a17d-fecaa57efc82':
|
case 'dc94d745-1262-4e63-a17d-fecaa57efc82':
|
||||||
title = i18n.ts._remoteLookupErrors._noSuchObject.title;
|
title = i18n.ts._remoteLookupErrors._noSuchObject.title;
|
||||||
text = i18n.ts._remoteLookupErrors._noSuchObject.description;
|
text = i18n.ts._remoteLookupErrors._noSuchObject.description;
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/matter-js": "0.19.8",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"execa": "9.5.2",
|
"execa": "9.5.2",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@readme/openapi-parser": "2.7.0",
|
"@readme/openapi-parser": "2.7.0",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"openapi-typescript": "6.7.6",
|
"openapi-typescript": "6.7.6",
|
||||||
"ts-case-convert": "2.1.0",
|
"ts-case-convert": "2.1.0",
|
||||||
"tsx": "4.19.2",
|
"tsx": "4.19.3",
|
||||||
"typescript": "5.7.3"
|
"typescript": "5.7.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.2.0",
|
"version": "2025.2.1-beta.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
@ -35,12 +35,12 @@
|
||||||
"directory": "packages/misskey-js"
|
"directory": "packages/misskey-js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "7.50.0",
|
"@microsoft/api-extractor": "7.50.1",
|
||||||
"@swc/jest": "0.2.37",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-fetch-mock": "3.0.3",
|
"jest-fetch-mock": "3.0.3",
|
||||||
"jest-websocket-mock": "2.5.0",
|
"jest-websocket-mock": "2.5.0",
|
||||||
|
|
|
@ -22,9 +22,9 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"execa": "9.5.2",
|
"execa": "9.5.2",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "8.24.0",
|
"@typescript-eslint/parser": "8.24.1",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
|
|
1883
pnpm-lock.yaml
1883
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
122
renovate.json5
122
renovate.json5
|
@ -1,85 +1,85 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||||
"extends": [
|
extends: [
|
||||||
"config:recommended"
|
'config:recommended',
|
||||||
],
|
],
|
||||||
"timezone": "Asia/Tokyo",
|
timezone: 'Asia/Tokyo',
|
||||||
"schedule": [
|
schedule: [
|
||||||
"* 0 * * *"
|
'* 0 * * *',
|
||||||
],
|
],
|
||||||
"prHourlyLimit": 5,
|
prHourlyLimit: 5,
|
||||||
"dependencyDashboardApproval": true,
|
dependencyDashboardApproval: true,
|
||||||
"dependencyDashboardAutoclose": true,
|
dependencyDashboardAutoclose: true,
|
||||||
"osvVulnerabilityAlerts": true,
|
osvVulnerabilityAlerts: true,
|
||||||
"dependencyDashboardOSVVulnerabilitySummary": "unresolved",
|
dependencyDashboardOSVVulnerabilitySummary: 'unresolved',
|
||||||
"ignoreDeps": [
|
ignoreDeps: [
|
||||||
// https://github.com/misskey-dev/misskey/pull/15489#issuecomment-2660717458
|
// https://github.com/misskey-dev/misskey/pull/15489#issuecomment-2660717458
|
||||||
"@typescript/lib-webworker",
|
'@typescript/lib-webworker',
|
||||||
// https://github.com/misskey-dev/misskey/pull/15494#issuecomment-2660775258
|
// https://github.com/misskey-dev/misskey/pull/15494#issuecomment-2660775258
|
||||||
"nsfwjs",
|
'nsfwjs',
|
||||||
],
|
],
|
||||||
"packageRules": [
|
packageRules: [
|
||||||
{
|
{
|
||||||
"groupName": "[Backend] Update dependencies",
|
groupName: '[Backend] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
"packages/backend/**/package.json"
|
'packages/backend/**/package.json',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[Frontend] Update dependencies",
|
groupName: '[Frontend] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
"packages/frontend/**/package.json",
|
'packages/frontend/**/package.json',
|
||||||
"packages/frontend-embed/**/package.json",
|
'packages/frontend-embed/**/package.json',
|
||||||
"packages/frontend-shared/**/package.json",
|
'packages/frontend-shared/**/package.json',
|
||||||
"packages/misskey-bubble-game/**/package.json",
|
'packages/misskey-bubble-game/**/package.json',
|
||||||
"packages/misskey-reversi/**/package.json",
|
'packages/misskey-reversi/**/package.json',
|
||||||
"packages/sw/**/package.json"
|
'packages/sw/**/package.json',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[misskey-js] Update dependencies",
|
groupName: '[misskey-js] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
"packages/misskey-js/**/package.json"
|
'packages/misskey-js/**/package.json',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[Root] Update dependencies",
|
groupName: '[Root] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
"package.json"
|
'package.json',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[Tools] Update dependencies",
|
groupName: '[Tools] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
"scripts/**/package.json"
|
'scripts/**/package.json',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[GitHub Actions] Update dependencies",
|
groupName: '[GitHub Actions] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
".github/workflows/**/*.yml"
|
'.github/workflows/**/*.yml',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[Node.js] Update dependencies",
|
groupName: '[Node.js] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
".node-version"
|
'.node-version',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[Docker] Update dependencies",
|
groupName: '[Docker] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
"compose.local-db.yml",
|
'compose.local-db.yml',
|
||||||
"compose_example.yml",
|
'compose_example.yml',
|
||||||
"packages/backend/test-federation/*.yml",
|
'packages/backend/test-federation/*.yml',
|
||||||
"Dockerfile"
|
'Dockerfile',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "[devcontainer] Update dependencies",
|
groupName: '[devcontainer] Update dependencies',
|
||||||
"matchPaths": [
|
matchFileNames: [
|
||||||
".devcontainer/**"
|
'.devcontainer/**',
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,8 @@ async function buildBackendScript() {
|
||||||
'./packages/backend/src/server/web/boot.js',
|
'./packages/backend/src/server/web/boot.js',
|
||||||
'./packages/backend/src/server/web/boot.embed.js',
|
'./packages/backend/src/server/web/boot.embed.js',
|
||||||
'./packages/backend/src/server/web/bios.js',
|
'./packages/backend/src/server/web/bios.js',
|
||||||
'./packages/backend/src/server/web/cli.js'
|
'./packages/backend/src/server/web/cli.js',
|
||||||
|
'./packages/backend/src/server/web/error.js',
|
||||||
]) {
|
]) {
|
||||||
let source = await fs.readFile(file, { encoding: 'utf-8' });
|
let source = await fs.readFile(file, { encoding: 'utf-8' });
|
||||||
source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
|
source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
|
||||||
|
|
|
@ -9,16 +9,16 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mdast": "4.0.4",
|
"@types/mdast": "4.0.4",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@vitest/coverage-v8": "3.0.5",
|
"@vitest/coverage-v8": "3.0.6",
|
||||||
"mdast-util-to-string": "4.0.0",
|
"mdast-util-to-string": "4.0.0",
|
||||||
"remark": "15.0.1",
|
"remark": "15.0.1",
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"unified": "11.0.5",
|
"unified": "11.0.5",
|
||||||
"vite": "6.1.0",
|
"vite": "6.1.1",
|
||||||
"vite-node": "3.0.5",
|
"vite-node": "3.0.6",
|
||||||
"vitest": "3.0.5"
|
"vitest": "3.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
|
@ -909,9 +909,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.4",
|
"version": "22.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz",
|
||||||
"integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
|
"integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -925,9 +925,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/coverage-v8": {
|
"node_modules/@vitest/coverage-v8": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.6.tgz",
|
||||||
"integrity": "sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==",
|
"integrity": "sha512-JRTlR8Bw+4BcmVTICa7tJsxqphAktakiLsAmibVLAWbu1lauFddY/tXeM6sAyl1cgkPuXtpnUgaCPhTdz1Qapg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -948,8 +948,8 @@
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vitest/browser": "3.0.5",
|
"@vitest/browser": "3.0.6",
|
||||||
"vitest": "3.0.5"
|
"vitest": "3.0.6"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vitest/browser": {
|
"@vitest/browser": {
|
||||||
|
@ -958,15 +958,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.6.tgz",
|
||||||
"integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==",
|
"integrity": "sha512-zBduHf/ja7/QRX4HdP1DSq5XrPgdN+jzLOwaTq/0qZjYfgETNFCKf9nOAp2j3hmom3oTbczuUzrzg9Hafh7hNg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "3.0.5",
|
"@vitest/spy": "3.0.6",
|
||||||
"@vitest/utils": "3.0.5",
|
"@vitest/utils": "3.0.6",
|
||||||
"chai": "^5.1.2",
|
"chai": "^5.2.0",
|
||||||
"tinyrainbow": "^2.0.0"
|
"tinyrainbow": "^2.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
|
@ -974,13 +974,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/mocker": {
|
"node_modules/@vitest/mocker": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.6.tgz",
|
||||||
"integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==",
|
"integrity": "sha512-KPztr4/tn7qDGZfqlSPQoF2VgJcKxnDNhmfR3VgZ6Fy1bO8T9Fc1stUiTXtqz0yG24VpD00pZP5f8EOFknjNuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "3.0.5",
|
"@vitest/spy": "3.0.6",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"magic-string": "^0.30.17"
|
"magic-string": "^0.30.17"
|
||||||
},
|
},
|
||||||
|
@ -1001,9 +1001,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/pretty-format": {
|
"node_modules/@vitest/pretty-format": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.6.tgz",
|
||||||
"integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==",
|
"integrity": "sha512-Zyctv3dbNL+67qtHfRnUE/k8qxduOamRfAL1BurEIQSyOEFffoMvx2pnDSSbKAAVxY0Ej2J/GH2dQKI0W2JyVg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1014,52 +1014,38 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner": {
|
"node_modules/@vitest/runner": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.6.tgz",
|
||||||
"integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==",
|
"integrity": "sha512-JopP4m/jGoaG1+CBqubV/5VMbi7L+NQCJTu1J1Pf6YaUbk7bZtaq5CX7p+8sY64Sjn1UQ1XJparHfcvTTdu9cA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "3.0.5",
|
"@vitest/utils": "3.0.6",
|
||||||
"pathe": "^2.0.2"
|
"pathe": "^2.0.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner/node_modules/pathe": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.6.tgz",
|
||||||
"integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==",
|
"integrity": "sha512-qKSmxNQwT60kNwwJHMVwavvZsMGXWmngD023OHSgn873pV0lylK7dwBTfYP7e4URy5NiBCHHiQGA9DHkYkqRqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "3.0.5",
|
"@vitest/pretty-format": "3.0.6",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"pathe": "^2.0.2"
|
"pathe": "^2.0.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/snapshot/node_modules/pathe": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@vitest/spy": {
|
"node_modules/@vitest/spy": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.6.tgz",
|
||||||
"integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==",
|
"integrity": "sha512-HfOGx/bXtjy24fDlTOpgiAEJbRfFxoX3zIGagCqACkFKKZ/TTOE6gYMKXlqecvxEndKFuNHcHqP081ggZ2yM0Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1070,14 +1056,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/utils": {
|
"node_modules/@vitest/utils": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.6.tgz",
|
||||||
"integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==",
|
"integrity": "sha512-18ktZpf4GQFTbf9jK543uspU03Q2qya7ZGya5yiZ0Gx0nnnalBvd5ZBislbl2EhLjM8A8rt4OilqKG7QwcGkvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "3.0.5",
|
"@vitest/pretty-format": "3.0.6",
|
||||||
"loupe": "^3.1.2",
|
"loupe": "^3.1.3",
|
||||||
"tinyrainbow": "^2.0.0"
|
"tinyrainbow": "^2.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
|
@ -2212,6 +2198,13 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pathval": {
|
"node_modules/pathval": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
|
||||||
|
@ -2731,14 +2724,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz",
|
||||||
"integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==",
|
"integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.24.2",
|
"esbuild": "^0.24.2",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.2",
|
||||||
"rollup": "^4.30.1"
|
"rollup": "^4.30.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -2803,16 +2796,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-node": {
|
"node_modules/vite-node": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.6.tgz",
|
||||||
"integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==",
|
"integrity": "sha512-s51RzrTkXKJrhNbUzQRsarjmAae7VmMPAsRT7lppVpIg6mK3zGthP9Hgz0YQQKuNcF+Ii7DfYk3Fxz40jRmePw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"es-module-lexer": "^1.6.0",
|
"es-module-lexer": "^1.6.0",
|
||||||
"pathe": "^2.0.2",
|
"pathe": "^2.0.3",
|
||||||
"vite": "^5.0.0 || ^6.0.0"
|
"vite": "^5.0.0 || ^6.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -2825,39 +2818,32 @@
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-node/node_modules/pathe": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.6.tgz",
|
||||||
"integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==",
|
"integrity": "sha512-/iL1Sc5VeDZKPDe58oGK4HUFLhw6b5XdY1MYawjuSaDA4sEfYlY9HnS6aCEG26fX+MgUi7MwlduTBHHAI/OvMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "3.0.5",
|
"@vitest/expect": "3.0.6",
|
||||||
"@vitest/mocker": "3.0.5",
|
"@vitest/mocker": "3.0.6",
|
||||||
"@vitest/pretty-format": "^3.0.5",
|
"@vitest/pretty-format": "^3.0.6",
|
||||||
"@vitest/runner": "3.0.5",
|
"@vitest/runner": "3.0.6",
|
||||||
"@vitest/snapshot": "3.0.5",
|
"@vitest/snapshot": "3.0.6",
|
||||||
"@vitest/spy": "3.0.5",
|
"@vitest/spy": "3.0.6",
|
||||||
"@vitest/utils": "3.0.5",
|
"@vitest/utils": "3.0.6",
|
||||||
"chai": "^5.1.2",
|
"chai": "^5.2.0",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"expect-type": "^1.1.0",
|
"expect-type": "^1.1.0",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"pathe": "^2.0.2",
|
"pathe": "^2.0.3",
|
||||||
"std-env": "^3.8.0",
|
"std-env": "^3.8.0",
|
||||||
"tinybench": "^2.9.0",
|
"tinybench": "^2.9.0",
|
||||||
"tinyexec": "^0.3.2",
|
"tinyexec": "^0.3.2",
|
||||||
"tinypool": "^1.0.2",
|
"tinypool": "^1.0.2",
|
||||||
"tinyrainbow": "^2.0.0",
|
"tinyrainbow": "^2.0.0",
|
||||||
"vite": "^5.0.0 || ^6.0.0",
|
"vite": "^5.0.0 || ^6.0.0",
|
||||||
"vite-node": "3.0.5",
|
"vite-node": "3.0.6",
|
||||||
"why-is-node-running": "^2.3.0"
|
"why-is-node-running": "^2.3.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -2873,8 +2859,8 @@
|
||||||
"@edge-runtime/vm": "*",
|
"@edge-runtime/vm": "*",
|
||||||
"@types/debug": "^4.1.12",
|
"@types/debug": "^4.1.12",
|
||||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||||
"@vitest/browser": "3.0.5",
|
"@vitest/browser": "3.0.6",
|
||||||
"@vitest/ui": "3.0.5",
|
"@vitest/ui": "3.0.6",
|
||||||
"happy-dom": "*",
|
"happy-dom": "*",
|
||||||
"jsdom": "*"
|
"jsdom": "*"
|
||||||
},
|
},
|
||||||
|
@ -2902,13 +2888,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest/node_modules/pathe": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|
|
@ -10,15 +10,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mdast": "4.0.4",
|
"@types/mdast": "4.0.4",
|
||||||
"@types/node": "22.13.4",
|
"@types/node": "22.13.5",
|
||||||
"@vitest/coverage-v8": "3.0.5",
|
"@vitest/coverage-v8": "3.0.6",
|
||||||
"mdast-util-to-string": "4.0.0",
|
"mdast-util-to-string": "4.0.0",
|
||||||
"remark": "15.0.1",
|
"remark": "15.0.1",
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"unified": "11.0.5",
|
"unified": "11.0.5",
|
||||||
"vite": "6.1.0",
|
"vite": "6.1.1",
|
||||||
"vite-node": "3.0.5",
|
"vite-node": "3.0.6",
|
||||||
"vitest": "3.0.5"
|
"vitest": "3.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue