1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-06-10 01:24:08 +02:00

Merge branch 'develop' into room

This commit is contained in:
syuilo
2026-05-07 11:33:46 +09:00
81 changed files with 2599 additions and 3513 deletions

View File

@@ -53,8 +53,8 @@
"utf-8-validate": "6.0.6"
},
"dependencies": {
"@aws-sdk/client-s3": "3.1030.0",
"@aws-sdk/lib-storage": "3.1030.0",
"@aws-sdk/client-s3": "3.1037.0",
"@aws-sdk/lib-storage": "3.1037.0",
"@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.4",
"@fastify/cors": "11.2.0",
@@ -64,27 +64,27 @@
"@fastify/static": "9.1.3",
"@kitajs/html": "4.2.13",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.5",
"@napi-rs/canvas": "0.1.97",
"@misskey-dev/summaly": "5.3.0",
"@napi-rs/canvas": "0.1.100",
"@nestjs/common": "11.1.19",
"@nestjs/core": "11.1.19",
"@nestjs/testing": "11.1.19",
"@oxc-project/runtime": "0.125.0",
"@oxc-project/runtime": "0.127.0",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "10.48.0",
"@sentry/profiling-node": "10.48.0",
"@sentry/node": "10.50.0",
"@sentry/profiling-node": "10.50.0",
"@simplewebauthn/server": "13.3.0",
"@sinonjs/fake-timers": "15.3.2",
"@smithy/node-http-handler": "4.5.2",
"@smithy/node-http-handler": "4.6.1",
"@twemoji/parser": "16.0.0",
"accepts": "1.3.8",
"ajv": "8.18.0",
"ajv": "8.20.0",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"bcryptjs": "3.0.3",
"blurhash": "2.0.5",
"body-parser": "2.2.2",
"bullmq": "5.73.5",
"bullmq": "5.76.2",
"cacheable-lookup": "7.0.0",
"chalk": "5.6.2",
"chalk-template": "1.1.2",
@@ -92,14 +92,14 @@
"color-convert": "3.1.3",
"content-disposition": "1.1.0",
"date-fns": "4.1.0",
"deep-email-validator": "0.1.21",
"deep-email-validator": "0.1.27",
"fastify": "5.8.5",
"fastify-raw-body": "5.0.0",
"feed": "5.2.0",
"feed": "5.2.1",
"file-type": "22.0.1",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.5",
"got": "14.6.6",
"got": "15.0.3",
"hpagent": "1.2.0",
"http-link-header": "1.1.3",
"i18n": "workspace:*",
@@ -116,16 +116,16 @@
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.202508261828",
"nanoid": "5.1.7",
"nanoid": "5.1.9",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"node-html-parser": "7.1.0",
"nodemailer": "8.0.5",
"nodemailer": "8.0.6",
"nsfwjs": "4.3.0",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
"otpauth": "9.5.0",
"otpauth": "9.5.1",
"pg": "8.20.0",
"pkce-challenge": "6.0.0",
"probe-image-size": "7.2.3",
@@ -160,8 +160,7 @@
"@kitajs/ts-html-plugin": "4.1.4",
"@nestjs/platform-express": "11.1.19",
"@rollup/plugin-esm-shim": "0.1.8",
"@sentry/vue": "10.48.0",
"@simplewebauthn/types": "12.0.0",
"@sentry/vue": "10.50.0",
"@types/accepts": "1.3.7",
"@types/archiver": "7.0.0",
"@types/body-parser": "1.19.6",
@@ -192,9 +191,9 @@
"@types/vary": "1.1.3",
"@types/web-push": "3.6.4",
"@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@vitest/coverage-v8": "4.1.4",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"@vitest/coverage-v8": "4.1.5",
"aws-sdk-client-mock": "4.1.0",
"cbor": "10.0.12",
"cross-env": "10.1.0",
@@ -206,8 +205,8 @@
"rolldown": "1.0.0-rc.15",
"simple-oauth2": "5.1.0",
"supertest": "7.2.2",
"vite": "8.0.8",
"vitest": "4.1.4",
"vite": "8.0.10",
"vitest": "4.1.5",
"vitest-mock-extended": "4.0.0"
}
}

View File

@@ -118,8 +118,8 @@ class NotificationManager {
visibleUserIds = new Set(this.note.visibleUserIds);
break;
case 'followers': {
// TODO: フォロワー限定ノートにフォロワーではない人がメンションされた場合通知されるのが正しい挙動なのか確認(一部に挙動の不一致がありそう)。現状は通知されるためフィルタしない
// case 'followers': {
// const targetUserIds = this.queue.map(x => x.target);
// const followers = await this.followingsRepository.find({
// where: {
@@ -130,8 +130,9 @@ class NotificationManager {
// select: ['followerId'],
// });
// visibleUserIds = new Set(followers.map(f => f.followerId));
// break;
// }
visibleUserIds = null;
break;
}
default:
visibleUserIds = new Set();

View File

@@ -246,7 +246,8 @@ export class NotificationService implements OnApplicationShutdown {
private toXListId(id: string): string {
const { date, additional } = this.idService.parseFull(id);
return date.toString() + '-' + additional.toString();
// Redis Stream sequenceはunit64制約があるため、収まらない場合は下位64bitを取る
return date.toString() + '-' + BigInt.asUintN(64, additional).toString();
}
@bindThis

View File

@@ -47,6 +47,7 @@ export type RolePolicies = {
canSearchUsers: boolean;
canUseTranslator: boolean;
canHideAds: boolean;
canCreateChannel: boolean;
driveCapacityMb: number;
maxFileSizeMb: number;
alwaysMarkNsfw: boolean;
@@ -88,6 +89,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canSearchUsers: true,
canUseTranslator: true,
canHideAds: false,
canCreateChannel: true,
driveCapacityMb: 100,
maxFileSizeMb: 30,
alwaysMarkNsfw: false,
@@ -410,6 +412,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canSearchUsers: calc('canSearchUsers', vs => vs.some(v => v === true)),
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
canCreateChannel: calc('canCreateChannel', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
maxFileSizeMb: calc('maxFileSizeMb', vs => Math.max(...vs)),
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),

View File

@@ -24,7 +24,7 @@ import type {
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON,
} from '@simplewebauthn/types';
} from '@simplewebauthn/server';
@Injectable()
export class WebAuthnService {

View File

@@ -313,7 +313,8 @@ export class ApInboxService {
// アナウンス先が許可されているかチェック
if (!this.utilityService.isFederationAllowedUri(uri)) return;
const unlock = await acquireApObjectLock(this.redisClient, uri);
const activityUri = getApId(activity);
const unlock = await acquireApObjectLock(this.redisClient, activityUri);
try {
// 既に同じURIを持つものが登録されていないかチェック

View File

@@ -224,6 +224,10 @@ export const packedRolePoliciesSchema = {
type: 'boolean',
optional: false, nullable: false,
},
canCreateChannel: {
type: 'boolean',
optional: false, nullable: false,
},
driveCapacityMb: {
type: 'integer',
optional: false, nullable: false,

View File

@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import cors from '@fastify/cors';
import multipart from '@fastify/multipart';
import { ModuleRef } from '@nestjs/core';
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { AuthenticationResponseJSON } from '@simplewebauthn/server';
import type { Config } from '@/config.js';
import type { InstancesRepository, AccessTokensRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';

View File

@@ -28,7 +28,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { AuthenticationResponseJSON } from '@simplewebauthn/server';
import type { FastifyReply, FastifyRequest } from 'fastify';
@Injectable()

View File

@@ -23,7 +23,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import type { IdentifiableError } from '@/misc/identifiable-error.js';
import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { AuthenticationResponseJSON } from '@simplewebauthn/server';
import type { FastifyReply, FastifyRequest } from 'fastify';
@Injectable()

View File

@@ -22,6 +22,8 @@ export const meta = {
kind: 'write:channels',
requiredRolePolicy: 'canCreateChannel',
limit: {
duration: ms('1hour'),
max: 10,

View File

@@ -39,137 +39,6 @@ export const meta = {
res: {
type: 'object',
nullable: false,
optional: false,
properties: {
rp: {
type: 'object',
properties: {
id: {
type: 'string',
optional: true,
},
},
},
user: {
type: 'object',
properties: {
id: {
type: 'string',
},
name: {
type: 'string',
},
displayName: {
type: 'string',
},
},
},
challenge: {
type: 'string',
},
pubKeyCredParams: {
type: 'array',
items: {
type: 'object',
properties: {
type: {
type: 'string',
},
alg: {
type: 'number',
},
},
},
},
timeout: {
type: 'number',
nullable: true,
},
excludeCredentials: {
type: 'array',
nullable: true,
items: {
type: 'object',
properties: {
id: {
type: 'string',
},
type: {
type: 'string',
},
transports: {
type: 'array',
items: {
type: 'string',
enum: [
'ble',
'cable',
'hybrid',
'internal',
'nfc',
'smart-card',
'usb',
],
},
},
},
},
},
authenticatorSelection: {
type: 'object',
nullable: true,
properties: {
authenticatorAttachment: {
type: 'string',
enum: [
'cross-platform',
'platform',
],
},
requireResidentKey: {
type: 'boolean',
},
userVerification: {
type: 'string',
enum: [
'discouraged',
'preferred',
'required',
],
},
},
},
attestation: {
type: 'string',
nullable: true,
enum: [
'direct',
'enterprise',
'indirect',
'none',
null,
],
},
extensions: {
type: 'object',
nullable: true,
properties: {
appid: {
type: 'string',
nullable: true,
},
credProps: {
type: 'boolean',
nullable: true,
},
hmacCreateSecret: {
type: 'boolean',
nullable: true,
},
},
},
},
},
} as const;

View File

@@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import type { SummalyResult } from '@misskey-dev/summaly/built/summary.js';
import type { SummalyResult } from '@misskey-dev/summaly';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';

View File

@@ -1,6 +1,6 @@
services:
nginx:
image: nginx:1.29
image: nginx:1.30
volumes:
- type: bind
source: ./certificates/rootCA.crt

View File

@@ -18,7 +18,7 @@ import type {
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON,
} from '@simplewebauthn/types';
} from '@simplewebauthn/server';
import type * as misskey from 'misskey-js';
import { describe, beforeAll, test } from 'vitest';

View File

@@ -8,7 +8,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vites
import { mockDeep } from 'vitest-mock-extended';
import { Test, TestingModule } from '@nestjs/testing';
import { FastifyReply, FastifyRequest } from 'fastify';
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { AuthenticationResponseJSON } from '@simplewebauthn/server';
import { HttpHeader } from 'fastify/types/utils.js';
import { MiUser } from '@/models/User.js';
import { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';

View File

@@ -38,4 +38,12 @@ describe('misc:ulid', () => {
// id[16] = Z
expect(() => parseUlidFull('01KPS7S300ABCDEFZ000000000')).not.toThrow();
});
test('parseUlidFull - additional exceeds uint64 max (all-Z randomness)', () => {
// All 16 random chars = 'Z' (Crockford max) → 80-bit value > uint64 max
const { additional } = parseUlidFull('01ARZ3NDEKZZZZZZZZZZZZZZZZ');
const uint64Max = 2n ** 64n - 1n;
expect(additional > uint64Max).toBe(true);
expect(BigInt.asUintN(64, additional) <= uint64Max).toBe(true);
});
});

View File

@@ -12,15 +12,15 @@
"devDependencies": {
"@types/estree": "1.0.8",
"@types/node": "24.12.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"rollup": "4.60.1"
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"rollup": "4.60.2"
},
"dependencies": {
"estree-walker": "3.0.3",
"i18n": "workspace:*",
"magic-string": "0.30.21",
"rolldown": "1.0.0-rc.15",
"vite": "8.0.8"
"vite": "8.0.10"
}
}

View File

@@ -24,14 +24,14 @@
"mfm-js": "0.25.0",
"misskey-js": "workspace:*",
"punycode.js": "2.3.1",
"rollup": "4.60.1",
"rollup": "4.60.2",
"shiki": "4.0.2",
"tinycolor2": "1.6.0",
"uuid": "13.0.0",
"vue": "3.5.32"
"uuid": "14.0.0",
"vue": "3.5.33"
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.5",
"@misskey-dev/summaly": "5.3.0",
"@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0",
"@types/estree": "1.0.8",
@@ -40,26 +40,26 @@
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@vitest/coverage-v8": "4.1.4",
"@vue/runtime-core": "3.5.32",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"@vitest/coverage-v8": "4.1.5",
"@vue/runtime-core": "3.5.33",
"acorn": "8.16.0",
"cross-env": "10.1.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"eslint-plugin-vue": "10.9.0",
"happy-dom": "20.9.0",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
"msw": "2.13.3",
"msw": "2.13.6",
"prettier": "3.8.3",
"sass-embedded": "1.99.0",
"start-server-and-test": "3.0.2",
"tsx": "4.21.0",
"vite": "8.0.8",
"vite": "8.0.10",
"vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "3.2.6",
"vue-component-type-helpers": "3.2.7",
"vue-eslint-parser": "10.4.0",
"vue-tsc": "3.2.6"
"vue-tsc": "3.2.7"
}
}

View File

@@ -22,10 +22,10 @@
},
"devDependencies": {
"@types/node": "24.12.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"esbuild": "0.28.0",
"eslint-plugin-vue": "10.8.0",
"eslint-plugin-vue": "10.9.0",
"nodemon": "3.1.14",
"vue-eslint-parser": "10.4.0"
},
@@ -35,6 +35,6 @@
"dependencies": {
"i18n": "workspace:*",
"misskey-js": "workspace:*",
"vue": "3.5.32"
"vue": "3.5.33"
}
}

View File

@@ -22,10 +22,10 @@
"@babylonjs/loaders": "9.5.1",
"@babylonjs/materials": "9.5.1",
"@discordapp/twemoji": "16.0.1",
"@github/webauthn-json": "2.1.1",
"@mcaptcha/core-glue": "0.1.0-alpha-5",
"@misskey-dev/browser-image-resizer": "2024.1.0",
"@sentry/vue": "10.48.0",
"@sentry/vue": "10.50.0",
"@simplewebauthn/browser": "13.3.0",
"@syuilo/aiscript": "1.2.1",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0",
@@ -40,7 +40,7 @@
"chartjs-chart-matrix": "3.0.0",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.2.0",
"chromatic": "15.3.1",
"chromatic": "16.6.0",
"compare-versions": "6.1.1",
"cropperjs": "2.1.1",
"date-fns": "4.1.0",
@@ -57,7 +57,7 @@
"is-file-animated": "1.0.2",
"json5": "2.2.3",
"matter-js": "0.20.0",
"mediabunny": "1.40.1",
"mediabunny": "1.41.0",
"mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
@@ -69,15 +69,15 @@
"sanitize-html": "2.17.3",
"shiki": "4.0.2",
"textarea-caret": "3.1.0",
"three": "0.183.2",
"three": "0.184.0",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
"v-code-diff": "1.13.1",
"vue": "3.5.32",
"vue": "3.5.33",
"wanakana": "5.3.1"
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.5",
"@misskey-dev/summaly": "5.3.0",
"@rollup/plugin-json": "6.1.0",
"@rollup/pluginutils": "5.3.0",
"@storybook/addon-essentials": "8.6.18",
@@ -111,22 +111,22 @@
"@types/textarea-caret": "3.0.4",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@vitest/coverage-v8": "4.1.4",
"@vue/compiler-core": "3.5.32",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"@vitest/coverage-v8": "4.1.5",
"@vue/compiler-core": "3.5.33",
"acorn": "8.16.0",
"astring": "1.9.0",
"cross-env": "10.1.0",
"cypress": "15.13.1",
"cypress": "15.14.1",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"eslint-plugin-vue": "10.9.0",
"estree-walker": "3.0.3",
"happy-dom": "20.9.0",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
"minimatch": "10.2.5",
"msw": "2.13.3",
"msw": "2.13.6",
"msw-storybook-addon": "2.0.7",
"nodemon": "3.1.14",
"prettier": "3.8.3",
@@ -139,13 +139,13 @@
"storybook": "10.3.5",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.21.0",
"vite": "8.0.8",
"vite": "8.0.10",
"vite-plugin-glsl": "1.6.0",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "4.1.4",
"vitest": "4.1.5",
"vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.2.6",
"vue-component-type-helpers": "3.2.7",
"vue-eslint-parser": "10.4.0",
"vue-tsc": "3.2.6"
"vue-tsc": "3.2.7"
}
}

View File

@@ -135,6 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA :to="notePage(appearNote)">
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
</MkA>
<span style="margin-left: 0.5em;">
<span style="border: 1px solid var(--MI_THEME-divider); margin-right: 0.5em;"></span>
<i v-if="appearNote.visibility === 'public'" class="ti ti-world"></i>
<i v-else-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i>
<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
<span style="margin-left: 0.3em;">{{ i18n.ts._visibility[appearNote.visibility] }}</span>
</span>
</div>
<MkReactionsViewer
v-if="appearNote.reactionAcceptance !== 'likeOnly'"

View File

@@ -22,21 +22,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { get as webAuthnRequest } from '@github/webauthn-json/browser-ponyfill';
import { startAuthentication } from '@simplewebauthn/browser';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
import type { PublicKeyCredentialRequestOptionsJSON, AuthenticationResponseJSON } from '@simplewebauthn/browser';
const props = defineProps<{
credentialRequest: CredentialRequestOptions;
credentialRequest: PublicKeyCredentialRequestOptionsJSON;
isPerformingPasswordlessLogin?: boolean;
}>();
const emit = defineEmits<{
(ev: 'done', credential: AuthenticationPublicKeyCredential): void;
(ev: 'done', credential: AuthenticationResponseJSON): void;
(ev: 'useTotp'): void;
}>();
@@ -44,7 +44,7 @@ const queryingKey = ref(true);
async function queryKey() {
queryingKey.value = true;
await webAuthnRequest(props.credentialRequest)
await startAuthentication({ optionsJSON: props.credentialRequest })
.catch(() => {
return Promise.reject(null);
})

View File

@@ -67,8 +67,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import { supported as webAuthnSupported, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
import { browserSupportsWebAuthn } from '@simplewebauthn/browser';
import type { PublicKeyCredentialRequestOptionsJSON, AuthenticationResponseJSON } from '@simplewebauthn/browser';
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import type { PwResponse } from '@/components/MkSignin.password.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -108,21 +108,18 @@ const userInfo = ref<null | Misskey.entities.UserDetailed>(null);
const password = ref('');
//#region Passkey Passwordless
const credentialRequest = shallowRef<CredentialRequestOptions | null>(null);
const credentialRequest = shallowRef<PublicKeyCredentialRequestOptionsJSON | null>(null);
const passkeyContext = ref('');
const doingPasskeyFromInputPage = ref(false);
function onPasskeyLogin(): void {
if (webAuthnSupported()) {
if (browserSupportsWebAuthn()) {
doingPasskeyFromInputPage.value = true;
waiting.value = true;
misskeyApi('signin-with-passkey', {})
.then((res) => {
passkeyContext.value = res.context ?? '';
credentialRequest.value = parseRequestOptionsFromJSON({
// @ts-expect-error TODO: misskey-js由来の型@simplewebauthn/typesとフロントエンド由来の型@github/webauthn-jsonが合わない
publicKey: res.option,
});
credentialRequest.value = res.option;
page.value = 'passkey';
waiting.value = false;
@@ -131,12 +128,12 @@ function onPasskeyLogin(): void {
}
}
function onPasskeyDone(credential: AuthenticationPublicKeyCredential): void {
function onPasskeyDone(credential: AuthenticationResponseJSON): void {
waiting.value = true;
if (doingPasskeyFromInputPage.value) {
misskeyApi<Misskey.entities.SigninWithPasskeyResponse>('signin-with-passkey', {
credential: credential.toJSON(),
misskeyApi('signin-with-passkey', {
credential: credential,
context: passkeyContext.value,
}).then((res) => {
if (res.signinResponse == null) {
@@ -150,8 +147,7 @@ function onPasskeyDone(credential: AuthenticationPublicKeyCredential): void {
tryLogin({
username: userInfo.value.username,
password: password.value,
// @ts-expect-error TODO: misskey-js由来の型@simplewebauthn/typesとフロントエンド由来の型@github/webauthn-jsonが合わない
credential: credential.toJSON(),
credential: credential,
});
}
}
@@ -253,11 +249,8 @@ async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promi
break;
}
case 'passkey': {
if (webAuthnSupported()) {
credentialRequest.value = parseRequestOptionsFromJSON({
// @ts-expect-error TODO: misskey-js由来の型@simplewebauthn/typesとフロントエンド由来の型@github/webauthn-jsonが合わない
publicKey: res.authRequest,
});
if (browserSupportsWebAuthn()) {
credentialRequest.value = res.authRequest;
page.value = 'passkey';
} else {
page.value = 'totp';

View File

@@ -86,7 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue';
import { url as local } from '@@/js/config.js';
import { versatileLang } from '@@/js/intl-const.js';
import type { summaly } from '@misskey-dev/summaly';
import type { SummalyResult } from '@misskey-dev/summaly';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { deviceKind } from '@/utility/device-kind.js';
@@ -96,8 +96,6 @@ import { store } from '@/store.js';
import { prefer } from '@/preferences.js';
import { maybeMakeRelative } from '@@/js/url.js';
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
const props = withDefaults(defineProps<{
url: string;
detail?: boolean;

View File

@@ -161,6 +161,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</XFolder>
<XFolder v-if="matchQuery([i18n.ts._role._options.canCreateChannel, 'canCreateChannel'])" v-model:policyMeta="policyMetaModel.canCreateChannel" :isBaseRole="isBaseRole" :readonly="readonly">
<template #label>{{ i18n.ts._role._options.canCreateChannel }}</template>
<template #suffix>{{ valuesModel.canCreateChannel ? i18n.ts.yes : i18n.ts.no }}</template>
<template #default="{ disabled }">
<MkSwitch v-model="valuesModel.canCreateChannel" :disabled="disabled">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</template>
</XFolder>
<XFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])" v-model:policyMeta="policyMetaModel.driveCapacityMb" :isBaseRole="isBaseRole" :readonly="readonly">
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
<template #valueText>{{ valuesModel.driveCapacityMb }}MB</template>

View File

@@ -139,6 +139,7 @@ async function assign() {
await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt });
//role.users.push(user);
usersPaginator.reload();
}
async function unassign(userId: Misskey.entities.User['id'], ev: PointerEvent) {
@@ -149,6 +150,7 @@ async function unassign(userId: Misskey.entities.User['id'], ev: PointerEvent) {
action: async () => {
await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: userId });
//role.users = role.users.filter(u => u.id !== userId);
usersPaginator.reload();
},
}], ev.currentTarget ?? ev.target);
}

View File

@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination>
</div>
<div v-else-if="tab === 'owned'" class="_gaps">
<MkButton type="routerLink" primary rounded to="/channels/new"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton>
<MkButton v-if="$i?.policies.canCreateChannel" type="routerLink" primary rounded to="/channels/new"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton>
<MkPagination v-slot="{items}" :paginator="ownedPaginator">
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
@@ -74,6 +74,7 @@ import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
import { Paginator } from '@/utility/paginator.js';
import { $i } from '@/i.js';
const router = useRouter();

View File

@@ -48,11 +48,11 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._2fa.securityKeyInfo }}
</MkInfo>
<MkInfo v-if="!webAuthnSupported()" warn>
<MkInfo v-if="!browserSupportsWebAuthn()" warn>
{{ i18n.ts._2fa.securityKeyNotSupported }}
</MkInfo>
<MkInfo v-else-if="webAuthnSupported() && !$i.twoFactorEnabled" warn>
<MkInfo v-else-if="browserSupportsWebAuthn() && !$i.twoFactorEnabled" warn>
{{ i18n.ts._2fa.registerTOTPBeforeKey }}
</MkInfo>
@@ -83,8 +83,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent, computed } from 'vue';
import { supported as webAuthnSupported, create as webAuthnCreate, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
import { computed } from 'vue';
import { browserSupportsWebAuthn, startRegistration } from '@simplewebauthn/browser';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
@@ -196,12 +196,9 @@ async function addSecurityKey() {
const auth = await os.authenticateDialog();
if (auth.canceled) return;
const registrationOptions = parseCreationOptionsFromJSON({
// @ts-expect-error misskey-js側に型がない
publicKey: await os.apiWithDialog('i/2fa/register-key', {
password: auth.result.password,
token: auth.result.token,
}),
const registrationOptions = await os.apiWithDialog('i/2fa/register-key', {
password: auth.result.password,
token: auth.result.token,
});
const name = await os.inputText({
@@ -214,7 +211,7 @@ async function addSecurityKey() {
if (name.canceled) return;
const credential = await os.promiseDialog(
webAuthnCreate(registrationOptions),
startRegistration({ optionsJSON: registrationOptions }),
null,
() => {}, // ユーザーのキャンセルはrejectなのでエラーダイアログを出さない
i18n.ts._2fa.tapSecurityKey,
@@ -228,8 +225,7 @@ async function addSecurityKey() {
password: auth.result.password,
token: auth.result.token,
name: name.result,
// @ts-expect-error misskey-js側に型がない
credential: credential.toJSON(),
credential: credential,
});
}

View File

@@ -6,13 +6,11 @@
import { describe, test, assert, afterEach } from 'vitest';
import { render, cleanup, type RenderResult } from '@testing-library/vue';
import './init';
import type { summaly } from '@misskey-dev/summaly';
import type { SummalyResult } from '@misskey-dev/summaly';
import { components } from '@/components/index.js';
import { directives } from '@/directives/index.js';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
describe('MkUrlPreview', () => {
const renderPreviewBy = async (summary: Partial<SummalyResult>): Promise<RenderResult> => {
if (!summary.player) {

View File

@@ -30,8 +30,8 @@
"devDependencies": {
"@types/js-yaml": "4.0.9",
"@types/node": "24.12.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"chokidar": "5.0.0",
"esbuild": "0.28.0",
"execa": "9.6.1",

View File

@@ -8177,6 +8177,10 @@ export interface Locale extends ILocale {
* 翻訳機能の利用
*/
"canUseTranslator": string;
/**
* チャンネルの作成
*/
"canCreateChannel": string;
/**
* アイコンデコレーションの最大取付個数
*/

View File

@@ -13,8 +13,8 @@
"devDependencies": {
"@types/node": "24.12.2",
"@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2"
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0"
},
"dependencies": {
"@tabler/icons-webfont": "3.35.0",

View File

@@ -27,8 +27,8 @@
"@types/matter-js": "0.20.2",
"@types/node": "24.12.2",
"@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"esbuild": "0.28.0",
"execa": "9.6.1",
"nodemon": "3.1.14"

View File

@@ -4,11 +4,13 @@
```ts
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { AuthenticationResponseJSON } from '@simplewebauthn/browser';
import { EventEmitter } from 'eventemitter3';
import { Options } from 'reconnecting-websocket';
import type { PublicKeyCredentialRequestOptionsJSON as PublicKeyCredentialRequestOptionsJSON_2 } from '@simplewebauthn/types';
import type { PublicKeyCredentialCreationOptionsJSON as PublicKeyCredentialCreationOptionsJSON_2 } from '@simplewebauthn/browser';
import type { PublicKeyCredentialRequestOptionsJSON as PublicKeyCredentialRequestOptionsJSON_2 } from '@simplewebauthn/browser';
import _ReconnectingWebSocket from 'reconnecting-websocket';
import type { RegistrationResponseJSON } from '@simplewebauthn/browser';
// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts
//
@@ -1471,6 +1473,14 @@ export type Endpoints = Overwrite<Endpoints_2, {
};
};
};
'i/2fa/register-key': {
req: I2faRegisterKeyRequest;
res: I2faRegisterKeyResponse_2;
};
'i/2fa/key-done': {
req: I2faKeyDoneRequest_2;
res: I2faKeyDoneResponse;
};
'admin/roles/create': {
req: Overwrite<AdminRolesCreateRequest, {
policies: PartialRolePolicyOverride;
@@ -1510,6 +1520,8 @@ declare namespace entities {
SigninWithPasskeyRequest,
SigninWithPasskeyInitResponse,
SigninWithPasskeyResponse,
I2faRegisterKeyResponse_2 as I2faRegisterKeyResponse,
I2faKeyDoneRequest_2 as I2faKeyDoneRequest,
PartialRolePolicyOverride,
EmptyRequest,
EmptyResponse,
@@ -1911,13 +1923,11 @@ declare namespace entities {
IResponse,
I2faDoneRequest,
I2faDoneResponse,
I2faKeyDoneRequest,
I2faKeyDoneResponse,
I2faPasswordLessRequest,
I2faRegisterRequest,
I2faRegisterResponse,
I2faRegisterKeyRequest,
I2faRegisterKeyResponse,
I2faRemoveKeyRequest,
I2faUnregisterRequest,
I2faUpdateKeyRequest,
@@ -2515,7 +2525,12 @@ type I2faDoneRequest = operations['i___2fa___done']['requestBody']['content']['a
type I2faDoneResponse = operations['i___2fa___done']['responses']['200']['content']['application/json'];
// @public (undocumented)
type I2faKeyDoneRequest = operations['i___2fa___key-done']['requestBody']['content']['application/json'];
type I2faKeyDoneRequest_2 = {
password: string;
token?: string | null;
name: string;
credential: RegistrationResponseJSON;
};
// @public (undocumented)
type I2faKeyDoneResponse = operations['i___2fa___key-done']['responses']['200']['content']['application/json'];
@@ -2527,7 +2542,7 @@ type I2faPasswordLessRequest = operations['i___2fa___password-less']['requestBod
type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json'];
type I2faRegisterKeyResponse_2 = PublicKeyCredentialCreationOptionsJSON_2;
// @public (undocumented)
type I2faRegisterRequest = operations['i___2fa___register']['requestBody']['content']['application/json'];
@@ -3466,7 +3481,7 @@ type RoleLite = components['schemas']['RoleLite'];
type RolePolicies = components['schemas']['RolePolicies'];
// @public (undocumented)
export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"];
export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "canCreateChannel", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"];
// @public (undocumented)
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
@@ -3880,7 +3895,7 @@ type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['
// Warnings were encountered during analysis:
//
// src/entities.ts:55:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/entities.ts:60:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:226:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:241:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts

View File

@@ -9,8 +9,8 @@
"devDependencies": {
"@readme/openapi-parser": "6.0.1",
"@types/node": "24.12.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"openapi-types": "12.1.3",
"openapi-typescript": "7.13.0",
"ts-case-convert": "2.1.0",

View File

@@ -414,7 +414,7 @@ async function main() {
await generateEndpoints(openApiDocs, typeFileName, entitiesFileName, endpointFileName);
const apiClientWarningFileName = `${generatePath}/apiClientJSDoc.ts`;
await generateApiClientJSDoc(openApiDocs, '../api.ts', endpointFileName, apiClientWarningFileName);
await generateApiClientJSDoc(openApiDocs, '../api.ts', '../api.types.ts', apiClientWarningFileName);
}
main();

View File

@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2026.5.0",
"version": "2026.5.1",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
@@ -37,24 +37,24 @@
"directory": "packages/misskey-js"
},
"devDependencies": {
"@microsoft/api-extractor": "7.58.2",
"@microsoft/api-extractor": "7.58.7",
"@types/node": "24.12.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@vitest/coverage-v8": "4.1.4",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"@vitest/coverage-v8": "4.1.5",
"esbuild": "0.28.0",
"execa": "9.6.1",
"ncp": "2.0.0",
"nodemon": "3.1.14",
"tsd": "0.33.0",
"vitest": "4.1.4",
"vitest": "4.1.5",
"vitest-websocket-mock": "0.5.0"
},
"files": [
"built"
],
"dependencies": {
"@simplewebauthn/types": "12.0.0",
"@simplewebauthn/browser": "13.3.0",
"eventemitter3": "5.0.4",
"reconnecting-websocket": "4.4.0"
}

View File

@@ -5,6 +5,8 @@ import {
AdminRolesCreateResponse,
EmptyRequest,
EmptyResponse,
I2faRegisterKeyRequest,
I2faKeyDoneResponse,
UsersShowRequest,
} from './autogen/entities.js';
import {
@@ -18,6 +20,8 @@ import {
SignupPendingResponse,
SignupRequest,
SignupResponse,
I2faRegisterKeyResponse,
I2faKeyDoneRequest,
} from './entities.js';
type Overwrite<T, U extends { [Key in keyof T]?: unknown }> = Omit<
@@ -109,6 +113,14 @@ export type Endpoints = Overwrite<
},
},
},
'i/2fa/register-key': {
req: I2faRegisterKeyRequest;
res: I2faRegisterKeyResponse;
},
'i/2fa/key-done': {
req: I2faKeyDoneRequest;
res: I2faKeyDoneResponse;
},
'admin/roles/create': {
req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>;
res: AdminRolesCreateResponse;

View File

@@ -1,5 +1,5 @@
import type { SwitchCaseResponseType } from '../api.js';
import type { Endpoints } from './endpoint.js';
import type { Endpoints } from '../api.types.js';
declare module '../api.js' {
export interface APIClient {

View File

@@ -5322,6 +5322,7 @@ export type components = {
canSearchUsers: boolean;
canUseTranslator: boolean;
canHideAds: boolean;
canCreateChannel: boolean;
driveCapacityMb: number;
maxFileSizeMb: number;
uploadableFileTypes: string[];
@@ -24445,41 +24446,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
'application/json': {
rp: {
id?: string;
};
user: {
id: string;
name: string;
displayName: string;
};
challenge: string;
pubKeyCredParams: {
type: string;
alg: number;
}[];
timeout: number | null;
excludeCredentials: {
id: string;
type: string;
transports: ('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[];
}[] | null;
authenticatorSelection: {
/** @enum {string} */
authenticatorAttachment: 'cross-platform' | 'platform';
requireResidentKey: boolean;
/** @enum {string} */
userVerification: 'discouraged' | 'preferred' | 'required';
} | null;
/** @enum {string|null} */
attestation: 'direct' | 'enterprise' | 'indirect' | 'none' | null;
extensions: {
appid: string | null;
credProps: boolean | null;
hmacCreateSecret: boolean | null;
} | null;
};
'application/json': Record<string, never>;
};
};
/** @description Client error */

View File

@@ -205,6 +205,7 @@ export const rolePolicies = [
'canSearchUsers',
'canUseTranslator',
'canHideAds',
'canCreateChannel',
'driveCapacityMb',
'maxFileSizeMb',
'alwaysMarkNsfw',

View File

@@ -10,7 +10,12 @@ import {
User,
UserDetailedNotMe,
} from './autogen/models.js';
import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
import type {
AuthenticationResponseJSON,
RegistrationResponseJSON,
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
} from '@simplewebauthn/browser';
export * from './autogen/entities.js';
export * from './autogen/models.js';
@@ -324,6 +329,15 @@ export type SigninWithPasskeyResponse = {
signinResponse: SigninFlowResponse & { finished: true };
};
export type I2faRegisterKeyResponse = PublicKeyCredentialCreationOptionsJSON;
export type I2faKeyDoneRequest = {
password: string;
token?: string | null;
name: string;
credential: RegistrationResponseJSON;
};
type Values<T extends Record<PropertyKey, unknown>> = T[keyof T];
export type PartialRolePolicyOverride = Partial<{ [k in keyof RolePolicies]: Omit<Values<Role['policies']>, 'value'> & { value: RolePolicies[k] } }>;

View File

@@ -25,8 +25,8 @@
},
"devDependencies": {
"@types/node": "24.12.2",
"@typescript-eslint/eslint-plugin": "8.58.2",
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/eslint-plugin": "8.59.0",
"@typescript-eslint/parser": "8.59.0",
"esbuild": "0.28.0",
"execa": "9.6.1",
"nodemon": "3.1.14"

View File

@@ -15,7 +15,7 @@
"misskey-js": "workspace:*"
},
"devDependencies": {
"@typescript-eslint/parser": "8.58.2",
"@typescript-eslint/parser": "8.59.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
"eslint-plugin-import": "2.32.0",
"nodemon": "3.1.14"