forked from mirrors/misskey
attempt to fix test
This commit is contained in:
parent
9d949618ba
commit
ebe92c9dd9
3 changed files with 116 additions and 27 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import { portToPid } from 'pid-port';
|
import { portToPid } from 'pid-port';
|
||||||
import fkill from 'fkill';
|
import fkill from 'fkill';
|
||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
|
import * as lolex from '@sinonjs/fake-timers';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { MainModule } from '@/MainModule.js';
|
import { MainModule } from '@/MainModule.js';
|
||||||
import { ServerService } from '@/server/ServerService.js';
|
import { ServerService } from '@/server/ServerService.js';
|
||||||
|
|
@ -10,11 +11,22 @@ import { INestApplicationContext } from '@nestjs/common';
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const originEnv = JSON.stringify(process.env);
|
const originEnv = JSON.stringify(process.env);
|
||||||
|
const originalDateNow = Date.now.bind(Date);
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
let app: INestApplicationContext;
|
let app: INestApplicationContext;
|
||||||
let serverService: ServerService;
|
let serverService: ServerService;
|
||||||
|
let baseNowMs = originalDateNow();
|
||||||
|
const clock = lolex.install({
|
||||||
|
toFake: Object.keys(lolex.timers).filter((key) => !['nextTick', 'queueMicrotask'].includes(key)) as lolex.FakeMethod[],
|
||||||
|
now: baseNowMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetFakeTime() {
|
||||||
|
baseNowMs = originalDateNow();
|
||||||
|
clock.setSystemTime(baseNowMs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* テスト用のサーバインスタンスを起動する
|
* テスト用のサーバインスタンスを起動する
|
||||||
|
|
@ -84,6 +96,7 @@ async function startControllerEndpoints(port = config.port + 1000) {
|
||||||
|
|
||||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
||||||
process.env = JSON.parse(originEnv);
|
process.env = JSON.parse(originEnv);
|
||||||
|
resetFakeTime();
|
||||||
|
|
||||||
await serverService.dispose();
|
await serverService.dispose();
|
||||||
await app.close();
|
await app.close();
|
||||||
|
|
@ -101,5 +114,30 @@ async function startControllerEndpoints(port = config.port + 1000) {
|
||||||
res.code(200).send({ success: true });
|
res.code(200).send({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.post<{ Body: { ms?: number } }>('/time/advance', async (req, res) => {
|
||||||
|
if (typeof req.body.ms !== 'number' || !Number.isFinite(req.body.ms)) {
|
||||||
|
res.code(400).send({ success: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clock.setSystemTime(Date.now() + req.body.ms);
|
||||||
|
|
||||||
|
res.code(200).send({
|
||||||
|
success: true,
|
||||||
|
now: Date.now(),
|
||||||
|
offsetMs: Date.now() - baseNowMs,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.post('/time/reset', async (_req, res) => {
|
||||||
|
resetFakeTime();
|
||||||
|
|
||||||
|
res.code(200).send({
|
||||||
|
success: true,
|
||||||
|
now: Date.now(),
|
||||||
|
offsetMs: Date.now() - baseNowMs,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await fastify.listen({ port: port, host: 'localhost' });
|
await fastify.listen({ port: port, host: 'localhost' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import * as crypto from 'node:crypto';
|
||||||
import cbor from 'cbor';
|
import cbor from 'cbor';
|
||||||
import * as OTPAuth from 'otpauth';
|
import * as OTPAuth from 'otpauth';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import { api, signup } from '../utils.js';
|
import { api, sendTimeAdvanceRequest, sendTimeResetRequest, signup } from '../utils.js';
|
||||||
import type {
|
import type {
|
||||||
AuthenticationResponseJSON,
|
AuthenticationResponseJSON,
|
||||||
AuthenticatorAssertionResponseJSON,
|
AuthenticatorAssertionResponseJSON,
|
||||||
|
|
@ -20,7 +20,7 @@ import type {
|
||||||
RegistrationResponseJSON,
|
RegistrationResponseJSON,
|
||||||
} from '@simplewebauthn/server';
|
} from '@simplewebauthn/server';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
import { describe, beforeAll, test } from 'vitest';
|
import { describe, beforeAll, beforeEach, test } from 'vitest';
|
||||||
|
|
||||||
describe('2要素認証', () => {
|
describe('2要素認証', () => {
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
|
|
@ -45,13 +45,19 @@ describe('2要素認証', () => {
|
||||||
'M0c+PVy4WGvCyMQ6SUWklvzo2+2osjqwsQ==\n' +
|
'M0c+PVy4WGvCyMQ6SUWklvzo2+2osjqwsQ==\n' +
|
||||||
'-----END EC PRIVATE KEY-----\n';
|
'-----END EC PRIVATE KEY-----\n';
|
||||||
|
|
||||||
const otpToken = (secret: string): string => {
|
const otpToken = (secret: string, timestamp?: number): string => {
|
||||||
return OTPAuth.TOTP.generate({
|
return OTPAuth.TOTP.generate({
|
||||||
secret: OTPAuth.Secret.fromBase32(secret),
|
secret: OTPAuth.Secret.fromBase32(secret),
|
||||||
digits: 6,
|
digits: 6,
|
||||||
|
timestamp,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nextOtpToken = async (secret: string): Promise<string> => {
|
||||||
|
const timestamp = await sendTimeAdvanceRequest({ ms: 30 * 1000 });
|
||||||
|
return otpToken(secret, timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
const rpIdHash = (): Buffer => {
|
const rpIdHash = (): Buffer => {
|
||||||
return crypto.createHash('sha256')
|
return crypto.createHash('sha256')
|
||||||
.update(Buffer.from(config.host, 'utf-8'))
|
.update(Buffer.from(config.host, 'utf-8'))
|
||||||
|
|
@ -181,6 +187,11 @@ describe('2要素認証', () => {
|
||||||
alice = await signup({ username, password });
|
alice = await signup({ username, password });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await sendTimeResetRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test('が設定でき、OTPでログインできる。', async () => {
|
test('が設定でき、OTPでログインできる。', async () => {
|
||||||
const registerResponse = await api('i/2fa/register', {
|
const registerResponse = await api('i/2fa/register', {
|
||||||
password,
|
password,
|
||||||
|
|
@ -193,7 +204,7 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(registerResponse.body.issuer, config.host);
|
assert.strictEqual(registerResponse.body.issuer, config.host);
|
||||||
|
|
||||||
const doneResponse = await api('i/2fa/done', {
|
const doneResponse = await api('i/2fa/done', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(doneResponse.status, 200);
|
assert.strictEqual(doneResponse.status, 200);
|
||||||
|
|
||||||
|
|
@ -208,7 +219,7 @@ describe('2要素認証', () => {
|
||||||
|
|
||||||
const signinResponse = await api('signin-flow', {
|
const signinResponse = await api('signin-flow', {
|
||||||
...signinParam(),
|
...signinParam(),
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
});
|
});
|
||||||
assert.strictEqual(signinResponse.status, 200);
|
assert.strictEqual(signinResponse.status, 200);
|
||||||
assert.strictEqual(signinResponse.body.finished, true);
|
assert.strictEqual(signinResponse.body.finished, true);
|
||||||
|
|
@ -217,7 +228,7 @@ describe('2要素認証', () => {
|
||||||
// 後片付け
|
// 後片付け
|
||||||
await api('i/2fa/unregister', {
|
await api('i/2fa/unregister', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -228,13 +239,13 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(registerResponse.status, 200);
|
assert.strictEqual(registerResponse.status, 200);
|
||||||
|
|
||||||
const doneResponse = await api('i/2fa/done', {
|
const doneResponse = await api('i/2fa/done', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(doneResponse.status, 200);
|
assert.strictEqual(doneResponse.status, 200);
|
||||||
|
|
||||||
const registerKeyResponse = await api('i/2fa/register-key', {
|
const registerKeyResponse = await api('i/2fa/register-key', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(registerKeyResponse.status, 200);
|
assert.strictEqual(registerKeyResponse.status, 200);
|
||||||
assert.notEqual(registerKeyResponse.body.rp, undefined);
|
assert.notEqual(registerKeyResponse.body.rp, undefined);
|
||||||
|
|
@ -243,7 +254,7 @@ describe('2要素認証', () => {
|
||||||
const keyName = 'example-key';
|
const keyName = 'example-key';
|
||||||
const credentialId = crypto.randomBytes(0x41);
|
const credentialId = crypto.randomBytes(0x41);
|
||||||
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
|
|
@ -274,7 +285,7 @@ describe('2要素認証', () => {
|
||||||
// 後片付け
|
// 後片付け
|
||||||
await api('i/2fa/unregister', {
|
await api('i/2fa/unregister', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -285,12 +296,12 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(registerResponse.status, 200);
|
assert.strictEqual(registerResponse.status, 200);
|
||||||
|
|
||||||
const doneResponse = await api('i/2fa/done', {
|
const doneResponse = await api('i/2fa/done', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(doneResponse.status, 200);
|
assert.strictEqual(doneResponse.status, 200);
|
||||||
|
|
||||||
const registerKeyResponse = await api('i/2fa/register-key', {
|
const registerKeyResponse = await api('i/2fa/register-key', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
password,
|
password,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(registerKeyResponse.status, 200);
|
assert.strictEqual(registerKeyResponse.status, 200);
|
||||||
|
|
@ -298,7 +309,7 @@ describe('2要素認証', () => {
|
||||||
const keyName = 'example-key';
|
const keyName = 'example-key';
|
||||||
const credentialId = crypto.randomBytes(0x41);
|
const credentialId = crypto.randomBytes(0x41);
|
||||||
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
|
|
@ -339,7 +350,7 @@ describe('2要素認証', () => {
|
||||||
// 後片付け
|
// 後片付け
|
||||||
await api('i/2fa/unregister', {
|
await api('i/2fa/unregister', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -350,12 +361,12 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(registerResponse.status, 200);
|
assert.strictEqual(registerResponse.status, 200);
|
||||||
|
|
||||||
const doneResponse = await api('i/2fa/done', {
|
const doneResponse = await api('i/2fa/done', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(doneResponse.status, 200);
|
assert.strictEqual(doneResponse.status, 200);
|
||||||
|
|
||||||
const registerKeyResponse = await api('i/2fa/register-key', {
|
const registerKeyResponse = await api('i/2fa/register-key', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
password,
|
password,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(registerKeyResponse.status, 200);
|
assert.strictEqual(registerKeyResponse.status, 200);
|
||||||
|
|
@ -363,7 +374,7 @@ describe('2要素認証', () => {
|
||||||
const keyName = 'example-key';
|
const keyName = 'example-key';
|
||||||
const credentialId = crypto.randomBytes(0x41);
|
const credentialId = crypto.randomBytes(0x41);
|
||||||
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
|
|
@ -389,7 +400,7 @@ describe('2要素認証', () => {
|
||||||
// 後片付け
|
// 後片付け
|
||||||
await api('i/2fa/unregister', {
|
await api('i/2fa/unregister', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -400,12 +411,12 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(registerResponse.status, 200);
|
assert.strictEqual(registerResponse.status, 200);
|
||||||
|
|
||||||
const doneResponse = await api('i/2fa/done', {
|
const doneResponse = await api('i/2fa/done', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(doneResponse.status, 200);
|
assert.strictEqual(doneResponse.status, 200);
|
||||||
|
|
||||||
const registerKeyResponse = await api('i/2fa/register-key', {
|
const registerKeyResponse = await api('i/2fa/register-key', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
password,
|
password,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(registerKeyResponse.status, 200);
|
assert.strictEqual(registerKeyResponse.status, 200);
|
||||||
|
|
@ -413,7 +424,7 @@ describe('2要素認証', () => {
|
||||||
const keyName = 'example-key';
|
const keyName = 'example-key';
|
||||||
const credentialId = crypto.randomBytes(0x41);
|
const credentialId = crypto.randomBytes(0x41);
|
||||||
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
|
|
@ -427,7 +438,7 @@ describe('2要素認証', () => {
|
||||||
assert.ok(beforeIResponse.body.securityKeysList);
|
assert.ok(beforeIResponse.body.securityKeysList);
|
||||||
for (const key of beforeIResponse.body.securityKeysList) {
|
for (const key of beforeIResponse.body.securityKeysList) {
|
||||||
const removeKeyResponse = await api('i/2fa/remove-key', {
|
const removeKeyResponse = await api('i/2fa/remove-key', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
password,
|
password,
|
||||||
credentialId: key.id,
|
credentialId: key.id,
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
@ -440,7 +451,7 @@ describe('2要素認証', () => {
|
||||||
|
|
||||||
const signinResponse = await api('signin-flow', {
|
const signinResponse = await api('signin-flow', {
|
||||||
...signinParam(),
|
...signinParam(),
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
});
|
});
|
||||||
assert.strictEqual(signinResponse.status, 200);
|
assert.strictEqual(signinResponse.status, 200);
|
||||||
assert.strictEqual(signinResponse.body.finished, true);
|
assert.strictEqual(signinResponse.body.finished, true);
|
||||||
|
|
@ -449,7 +460,7 @@ describe('2要素認証', () => {
|
||||||
// 後片付け
|
// 後片付け
|
||||||
await api('i/2fa/unregister', {
|
await api('i/2fa/unregister', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -460,7 +471,7 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(registerResponse.status, 200);
|
assert.strictEqual(registerResponse.status, 200);
|
||||||
|
|
||||||
const doneResponse = await api('i/2fa/done', {
|
const doneResponse = await api('i/2fa/done', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(doneResponse.status, 200);
|
assert.strictEqual(doneResponse.status, 200);
|
||||||
|
|
||||||
|
|
@ -469,7 +480,7 @@ describe('2要素認証', () => {
|
||||||
assert.strictEqual(iResponse.body.twoFactorEnabled, true);
|
assert.strictEqual(iResponse.body.twoFactorEnabled, true);
|
||||||
|
|
||||||
const unregisterResponse = await api('i/2fa/unregister', {
|
const unregisterResponse = await api('i/2fa/unregister', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
password,
|
password,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(unregisterResponse.status, 204);
|
assert.strictEqual(unregisterResponse.status, 204);
|
||||||
|
|
@ -484,7 +495,7 @@ describe('2要素認証', () => {
|
||||||
// 後片付け
|
// 後片付け
|
||||||
await api('i/2fa/unregister', {
|
await api('i/2fa/unregister', {
|
||||||
password,
|
password,
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: await nextOtpToken(registerResponse.body.secret),
|
||||||
}, alice);
|
}, alice);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -644,6 +644,46 @@ export async function sendEnvResetRequest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendTimeAdvanceRequest(params: { ms: number }): Promise<number> {
|
||||||
|
const res = await fetch(
|
||||||
|
`http://localhost:${port + 1000}/time/advance`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('server time advance failed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await res.json() as { now: number };
|
||||||
|
return body.now;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendTimeResetRequest(): Promise<number> {
|
||||||
|
const res = await fetch(
|
||||||
|
`http://localhost:${port + 1000}/time/reset`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('server time reset failed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await res.json() as { now: number };
|
||||||
|
return body.now;
|
||||||
|
}
|
||||||
|
|
||||||
// 与えられた値を強制的にエラーとみなす。この関数は型安全性を破壊するため、異常系のアサーション以外で用いられるべきではない。
|
// 与えられた値を強制的にエラーとみなす。この関数は型安全性を破壊するため、異常系のアサーション以外で用いられるべきではない。
|
||||||
// FIXME(misskey-js): misskey-jsがエラー情報を公開するようになったらこの関数を廃止する
|
// FIXME(misskey-js): misskey-jsがエラー情報を公開するようになったらこの関数を廃止する
|
||||||
export function castAsError(obj: Record<string, unknown>): { error: ApiError } {
|
export function castAsError(obj: Record<string, unknown>): { error: ApiError } {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue