spec(api): 一部APIをGETに対応・認証情報をヘッダーに (MisskeyIO#837)

This commit is contained in:
あわわわとーにゅ 2024-12-22 03:08:34 +09:00 committed by GitHub
parent 58513c1b81
commit 3362c464c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 155 additions and 26 deletions

View File

@ -11,6 +11,8 @@ export const meta = {
tags: ['meta'], tags: ['meta'],
requireCredential: false, requireCredential: false,
allowGet: true,
cacheSec: 60,
res: { res: {
type: 'object', type: 'object',

View File

@ -12,6 +12,8 @@ import UsersChart from '@/core/chart/charts/users.js';
export const meta = { export const meta = {
requireCredential: false, requireCredential: false,
allowGet: true,
cacheSec: 60,
tags: ['meta'], tags: ['meta'],

View File

@ -32,6 +32,11 @@
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.') renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.')
} }
if (localStorage.getItem('id') === null) {
localStorage.setItem('id', crypto.randomUUID().replaceAll('-', ''));
}
let id = localStorage.getItem('id');
//#region Detect language & fetch translations //#region Detect language & fetch translations
if (!Object.hasOwn(localStorage, 'locale')) { if (!Object.hasOwn(localStorage, 'locale')) {
let lang = localStorage.getItem('lang'); let lang = localStorage.getItem('lang');
@ -40,12 +45,11 @@
} }
const metaRes = await window.fetch('/api/meta', { const metaRes = await window.fetch('/api/meta', {
method: 'POST', method: 'GET',
body: JSON.stringify({}),
credentials: 'omit', credentials: 'omit',
cache: 'no-cache',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Client-Transaction-Id': `${id}-misskey-${crypto.randomUUID().replaceAll('-', '')}`
}, },
}); });
if (metaRes.status !== 200) { if (metaRes.status !== 200) {

View File

@ -62,10 +62,6 @@ export async function common(createVue: () => App<Element>) {
}); });
} }
if (miLocalStorage.getItem('id') === null) {
miLocalStorage.setItem('id', crypto.randomUUID());
}
let isClientUpdated = false; let isClientUpdated = false;
//#region クライアントが更新されたかチェック //#region クライアントが更新されたかチェック

View File

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onMounted, shallowRef, ref, nextTick } from 'vue'; import { onMounted, shallowRef, ref, nextTick } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { chartVLine } from '@/scripts/chart-vline.js'; import { chartVLine } from '@/scripts/chart-vline.js';
@ -52,7 +52,7 @@ async function renderChart() {
})); }));
}; };
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' }); const raw = await misskeyApiGet('charts/active-users', { limit: chartLimit, span: 'day' });
fetching.value = false; fetching.value = false;

View File

@ -79,7 +79,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import { instanceName } from '@/config.js'; import { instanceName } from '@/config.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js'; import { instance } from '@/instance.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
@ -89,7 +89,7 @@ import { openInstanceMenu } from '@/ui/_common_/common';
const stats = ref<Misskey.entities.StatsResponse | null>(null); const stats = ref<Misskey.entities.StatsResponse | null>(null);
misskeyApi('stats', {}).then((res) => { misskeyApiGet('stats').then((res) => {
stats.value = res; stats.value = res;
}); });

View File

@ -5,7 +5,7 @@
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js'; import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js';
@ -47,7 +47,7 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
} }
} }
const meta = await misskeyApi('meta', { const meta = await misskeyApiGet('meta', {
detail: true, detail: true,
}); });

View File

@ -164,7 +164,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import MkInstanceStats from '@/components/MkInstanceStats.vue'; import MkInstanceStats from '@/components/MkInstanceStats.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApiGet } from '@/scripts/misskey-api.js';
import number from '@/filters/number.js'; import number from '@/filters/number.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
@ -187,8 +187,7 @@ watch(tab, () => {
} }
}); });
const initStats = () => misskeyApi('stats', { const initStats = () => misskeyApiGet('stats').then((res) => {
}).then((res) => {
stats.value = res; stats.value = res;
}); });

View File

@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js'; import { misskeyApiGet } from '@/scripts/misskey-api.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue'; import MkNumberDiff from '@/components/MkNumberDiff.vue';
import MkNumber from '@/components/MkNumber.vue'; import MkNumber from '@/components/MkNumber.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -78,7 +78,7 @@ const fetching = ref(true);
onMounted(async () => { onMounted(async () => {
const [_stats, _onlineUsersCount] = await Promise.all([ const [_stats, _onlineUsersCount] = await Promise.all([
misskeyApi('stats', {}), misskeyApiGet('stats'),
misskeyApiGet('get-online-users-count').then(res => res.count), misskeyApiGet('get-online-users-count').then(res => res.count),
]); ]);
stats.value = _stats; stats.value = _stats;

View File

@ -15,11 +15,17 @@ export const pendingApiRequestsCount = ref(0);
let id: string | null = miLocalStorage.getItem('id'); let id: string | null = miLocalStorage.getItem('id');
export function generateClientTransactionId(initiator: string) { export function generateClientTransactionId(initiator: string) {
if (id === null) { if (id === null) {
id = crypto.randomUUID(); id = crypto.randomUUID().replaceAll('-', '');
miLocalStorage.setItem('id', id); miLocalStorage.setItem('id', id);
} }
return `${id}-${initiator}-${crypto.randomUUID()}`; // ハイフンが含まれている場合は除去
if (id.includes('-')) {
id = id.replaceAll('-', '');
miLocalStorage.setItem('id', id);
}
return `${id}-${initiator}-${crypto.randomUUID().replaceAll('-', '')}`;
} }
function handleResponse<_ResT>( function handleResponse<_ResT>(
@ -58,24 +64,22 @@ export function misskeyApi<
if (endpoint.includes('://')) throw new Error('invalid endpoint'); if (endpoint.includes('://')) throw new Error('invalid endpoint');
pendingApiRequestsCount.value++; pendingApiRequestsCount.value++;
const credential = token ? token : $i ? $i.token : undefined;
const onFinally = () => { const onFinally = () => {
pendingApiRequestsCount.value--; pendingApiRequestsCount.value--;
}; };
const promise = new Promise<_ResT>((resolve, reject) => { const promise = new Promise<_ResT>((resolve, reject) => {
// Append a credential
if ($i) (data as any).i = $i.token;
if (token !== undefined) (data as any).i = token;
// Send request // Send request
const initiateTime = Date.now(); const initiateTime = Date.now();
window.fetch(`${apiUrl}/${endpoint}`, { window.fetch(`${apiUrl}/${endpoint}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(data), body: JSON.stringify(data),
credentials: 'omit', credentials: 'omit',
cache: 'no-cache',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': credential ? `Bearer ${credential}` : 'anonymous',
'X-Client-Transaction-Id': generateClientTransactionId(initiator), 'X-Client-Transaction-Id': generateClientTransactionId(initiator),
}, },
signal, signal,
@ -121,8 +125,8 @@ export function misskeyApiGet<
window.fetch(`${apiUrl}/${endpoint}?${query}`, { window.fetch(`${apiUrl}/${endpoint}?${query}`, {
method: 'GET', method: 'GET',
credentials: 'omit', credentials: 'omit',
cache: 'default',
headers: { headers: {
'Authorization': 'anonymous',
'X-Client-Transaction-Id': generateClientTransactionId(initiator), 'X-Client-Transaction-Id': generateClientTransactionId(initiator),
}, },
}).then(res => { }).then(res => {

View File

@ -2535,6 +2535,13 @@ export type paths = {
post: operations['invite___limit']; post: operations['invite___limit'];
}; };
'/meta': { '/meta': {
/**
* meta
* @description No description provided.
*
* **Credential required**: *No*
*/
get: operations['meta_get'];
/** /**
* meta * meta
* @description No description provided. * @description No description provided.
@ -3229,6 +3236,13 @@ export type paths = {
post: operations['server-info']; post: operations['server-info'];
}; };
'/stats': { '/stats': {
/**
* stats
* @description No description provided.
*
* **Credential required**: *No*
*/
get: operations['stats_get'];
/** /**
* stats * stats
* @description No description provided. * @description No description provided.
@ -22271,6 +22285,60 @@ export type operations = {
}; };
}; };
}; };
/**
* meta
* @description No description provided.
*
* **Credential required**: *No*
*/
meta_get: {
requestBody: {
content: {
'application/json': {
/** @default true */
detail?: boolean;
};
};
};
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': components['schemas']['MetaLite'] | components['schemas']['MetaDetailed'];
};
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/** /**
* meta * meta
* @description No description provided. * @description No description provided.
@ -26776,6 +26844,60 @@ export type operations = {
}; };
}; };
}; };
/**
* stats
* @description No description provided.
*
* **Credential required**: *No*
*/
stats_get: {
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': {
notesCount: number;
originalNotesCount: number;
usersCount: number;
originalUsersCount: number;
instances: number;
driveUsageLocal: number;
driveUsageRemote: number;
};
};
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/** /**
* stats * stats
* @description No description provided. * @description No description provided.