enhance: embedページではstoreの保存先を完全に分離するように

This commit is contained in:
kakkokari-gtyih 2024-06-01 21:03:39 +09:00
parent ecf7945fe8
commit e9b3b5ffcd
13 changed files with 168 additions and 23 deletions

View file

@ -751,7 +751,7 @@ export class ClientServerService {
}); });
//#endregion //#endregion
//region noindex pages //#region noindex pages
// Tags // Tags
fastify.get<{ Params: { clip: string; } }>('/tags/:tag', async (request, reply) => { fastify.get<{ Params: { clip: string; } }>('/tags/:tag', async (request, reply) => {
return await renderBase(reply, { noindex: true }); return await renderBase(reply, { noindex: true });
@ -761,7 +761,13 @@ export class ClientServerService {
fastify.get<{ Params: { clip: string; } }>('/user-tags/:tag', async (request, reply) => { fastify.get<{ Params: { clip: string; } }>('/user-tags/:tag', async (request, reply) => {
return await renderBase(reply, { noindex: true }); return await renderBase(reply, { noindex: true });
}); });
//endregion //#endregion
//#region embed pages
fastify.get('/embed/:path(.*)', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
return await renderBase(reply, { noindex: true });
});
fastify.get('/_info_card_', async (request, reply) => { fastify.get('/_info_card_', async (request, reply) => {
const meta = await this.metaService.fetch(true); const meta = await this.metaService.fetch(true);
@ -776,6 +782,7 @@ export class ClientServerService {
originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }), originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
}); });
}); });
//#endregion
fastify.get('/bios', async (request, reply) => { fastify.get('/bios', async (request, reply) => {
return await reply.view('bios', { return await reply.view('bios', {

View file

@ -9,10 +9,19 @@ import 'vite/modulepreload-polyfill';
import '@/style.scss'; import '@/style.scss';
import { mainBoot } from '@/boot/main-boot.js'; import { mainBoot } from '@/boot/main-boot.js';
import { subBoot } from '@/boot/sub-boot.js'; import { subBoot } from '@/boot/sub-boot.js';
import { isEmbedPage } from '@/scripts/embed-page.js';
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete']; const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete', '/embed'];
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
if (isEmbedPage()) {
const params = new URLSearchParams(location.search);
const color = params.get('color');
if (color && ['light', 'dark'].includes(color)) {
subBoot({ forceColorMode: color as 'light' | 'dark' });
}
}
subBoot(); subBoot();
} else { } else {
mainBoot(); mainBoot();

View file

@ -14,6 +14,7 @@ import { apiUrl } from '@/config.js';
import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
import { isEmbedPage } from '@/scripts/embed-page.js';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
@ -21,8 +22,14 @@ type Account = Misskey.entities.MeDetailed & { token: string };
const accountData = miLocalStorage.getItem('account'); const accountData = miLocalStorage.getItem('account');
function initAccount() {
if (isEmbedPage()) return null;
if (accountData) return reactive(JSON.parse(accountData) as Account);
return null;
}
// TODO: 外部からはreadonlyに // TODO: 外部からはreadonlyに
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; export const $i = initAccount();
export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true); export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
export const iAmAdmin = $i != null && $i.isAdmin; export const iAmAdmin = $i != null && $i.isAdmin;
@ -78,10 +85,14 @@ export async function signout() {
} }
export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> {
if (isEmbedPage()) return [];
return (await get('accounts')) || []; return (await get('accounts')) || [];
} }
export async function addAccount(id: Account['id'], token: Account['token']) { export async function addAccount(id: Account['id'], token: Account['token']) {
if (isEmbedPage()) return;
const accounts = await getAccounts(); const accounts = await getAccounts();
if (!accounts.some(x => x.id === id)) { if (!accounts.some(x => x.id === id)) {
await set('accounts', accounts.concat([{ id, token }])); await set('accounts', accounts.concat([{ id, token }]));
@ -183,6 +194,8 @@ export async function refreshAccount() {
} }
export async function login(token: Account['token'], redirect?: string) { export async function login(token: Account['token'], redirect?: string) {
if (isEmbedPage()) return;
const showing = ref(true); const showing = ref(true);
popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), { popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), {
success: false, success: false,

View file

@ -24,7 +24,17 @@ import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js'; import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/router/definition.js'; import { setupRouter } from '@/router/definition.js';
export async function common(createVue: () => App<Element>) { export type CommonBootOptions = {
forceColorMode?: 'dark' | 'light' | 'auto';
};
const defaultCommonBootOptions: CommonBootOptions = {
forceColorMode: 'auto',
};
export async function common(createVue: () => App<Element>, partialOptions?: Partial<CommonBootOptions>) {
const bootOptions = Object.assign(defaultCommonBootOptions, partialOptions);
console.info(`Misskey v${version}`); console.info(`Misskey v${version}`);
if (_DEV_) { if (_DEV_) {
@ -166,15 +176,19 @@ export async function common(createVue: () => App<Element>) {
}); });
//#region Sync dark mode //#region Sync dark mode
if (ColdDeviceStorage.get('syncDeviceDarkMode')) { if (ColdDeviceStorage.get('syncDeviceDarkMode') && bootOptions.forceColorMode === 'auto') {
defaultStore.set('darkMode', isDeviceDarkmode()); defaultStore.set('darkMode', isDeviceDarkmode());
} }
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
if (ColdDeviceStorage.get('syncDeviceDarkMode')) { if (ColdDeviceStorage.get('syncDeviceDarkMode') && bootOptions.forceColorMode === 'auto') {
defaultStore.set('darkMode', mql.matches); defaultStore.set('darkMode', mql.matches);
} }
}); });
if (bootOptions.forceColorMode !== 'auto') {
defaultStore.set('darkMode', bootOptions.forceColorMode === 'dark');
}
//#endregion //#endregion
fetchInstanceMetaPromise.then(() => { fetchInstanceMetaPromise.then(() => {

View file

@ -5,9 +5,10 @@
import { createApp, defineAsyncComponent } from 'vue'; import { createApp, defineAsyncComponent } from 'vue';
import { common } from './common.js'; import { common } from './common.js';
import type { CommonBootOptions } from './common.js';
export async function subBoot() { export async function subBoot(options?: CommonBootOptions) {
const { isClientUpdated } = await common(() => createApp( const { isClientUpdated } = await common(() => createApp(
defineAsyncComponent(() => import('@/ui/minimum.vue')), defineAsyncComponent(() => import('@/ui/minimum.vue')),
)); ), options);
} }

View file

@ -2,8 +2,9 @@
* SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { isEmbedPage, initEmbedPageLocalStorage } from "@/scripts/embed-page.js";
type Keys = export type Keys =
'v' | 'v' |
'lastVersion' | 'lastVersion' |
'instance' | 'instance' |
@ -38,12 +39,33 @@ type Keys =
`aiscript:${string}` | `aiscript:${string}` |
'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~) 'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~); 'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
`channelLastReadedAt:${string}` `channelLastReadedAt:${string}` |
`idbfallback::${string}`
// セッション毎に廃棄されるLocalStorage代替embedなどで使用
const safeSessionStorage = new Map<Keys, string>();
export const miLocalStorage = { export const miLocalStorage = {
getItem: (key: Keys): string | null => window.localStorage.getItem(key), getItem: (key: Keys): string | null => {
setItem: (key: Keys, value: string): void => window.localStorage.setItem(key, value), if (isEmbedPage()) {
removeItem: (key: Keys): void => window.localStorage.removeItem(key), return safeSessionStorage.get(key) ?? null;
}
return window.localStorage.getItem(key);
},
setItem: (key: Keys, value: string): void => {
if (isEmbedPage()) {
safeSessionStorage.set(key, value);
} else {
window.localStorage.setItem(key, value);
}
},
removeItem: (key: Keys): void => {
if (isEmbedPage()) {
safeSessionStorage.delete(key);
} else {
window.localStorage.removeItem(key);
}
},
getItemAsJson: (key: Keys): any | undefined => { getItemAsJson: (key: Keys): any | undefined => {
const item = miLocalStorage.getItem(key); const item = miLocalStorage.getItem(key);
if (item === null) { if (item === null) {
@ -51,5 +73,12 @@ export const miLocalStorage = {
} }
return JSON.parse(item); return JSON.parse(item);
}, },
setItemAsJson: (key: Keys, value: any): void => window.localStorage.setItem(key, JSON.stringify(value)), setItemAsJson: (key: Keys, value: any): void => {
miLocalStorage.setItem(key, JSON.stringify(value));
},
}; };
if (isEmbedPage()) {
initEmbedPageLocalStorage();
if (_DEV_) console.warn('Using safeSessionStorage as localStorage alternative');
}

View file

@ -24,6 +24,7 @@ import MkContextMenu from '@/components/MkContextMenu.vue';
import { MenuItem } from '@/types/menu.js'; import { MenuItem } from '@/types/menu.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js'; import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { isEmbedPage } from '@/scripts/embed-page.js';
export const openingWindowsCount = ref(0); export const openingWindowsCount = ref(0);
@ -172,6 +173,8 @@ export async function popup<T extends Component>(
events: ComponentEmit<T> = {} as ComponentEmit<T>, events: ComponentEmit<T> = {} as ComponentEmit<T>,
disposeEvent?: keyof ComponentEmit<T>, disposeEvent?: keyof ComponentEmit<T>,
): Promise<{ dispose: () => void }> { ): Promise<{ dispose: () => void }> {
if (isEmbedPage()) return { dispose: () => {} };
markRaw(component); markRaw(component);
const id = ++popupIdCount; const id = ++popupIdCount;

View file

@ -0,0 +1,13 @@
<template>
<div>
$i: {{ JSON.stringify($i) }}
</div>
</template>
<script setup lang="ts">
import { $i } from '@/account.js';
</script>
<style scoped>
</style>

View file

@ -555,6 +555,10 @@ const routes: RouteDef[] = [{
path: '/reversi/g/:gameId', path: '/reversi/g/:gameId',
component: page(() => import('@/pages/reversi/game.vue')), component: page(() => import('@/pages/reversi/game.vue')),
loginRequired: false, loginRequired: false,
}, {
path: '/embed',
component: page(() => import('@/pages/embed/index.vue')),
// children: [],
}, { }, {
path: '/timeline', path: '/timeline',
component: page(() => import('@/pages/timeline.vue')), component: page(() => import('@/pages/timeline.vue')),

View file

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { miLocalStorage } from "@/local-storage.js";
import type { Keys } from "@/local-storage.js";
export function isEmbedPage() {
return location.pathname.startsWith('/embed');
}
/**
* EmbedページではlocalStorageを使用できないようにしているが
* safeSessionStoragemiLocalStorage内のやつ
*/
export function initEmbedPageLocalStorage() {
if (!isEmbedPage()) {
return;
}
const keysToDuplicate: Keys[] = [
'v',
'lastVersion',
'instance',
'instanceCachedAt',
'lang',
'locale',
'localeVersion',
];
keysToDuplicate.forEach(key => {
const value = window.localStorage.getItem(key);
if (value && !miLocalStorage.getItem(key)) {
miLocalStorage.setItem(key, value);
}
});
}

View file

@ -10,10 +10,12 @@ import {
set as iset, set as iset,
del as idel, del as idel,
} from 'idb-keyval'; } from 'idb-keyval';
import { isEmbedPage } from './embed-page.js';
import { miLocalStorage } from '@/local-storage.js';
const fallbackName = (key: string) => `idbfallback::${key}`; const PREFIX = 'idbfallback::';
let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && window.indexedDB.open) : true; let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && typeof window.indexedDB.open === 'function' && !isEmbedPage()) : true;
// iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。 // iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。
// バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと // バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと
@ -38,15 +40,15 @@ if (idbAvailable) {
export async function get(key: string) { export async function get(key: string) {
if (idbAvailable) return iget(key); if (idbAvailable) return iget(key);
return JSON.parse(window.localStorage.getItem(fallbackName(key))); return miLocalStorage.getItemAsJson(`${PREFIX}${key}`);
} }
export async function set(key: string, val: any) { export async function set(key: string, val: any) {
if (idbAvailable) return iset(key, val); if (idbAvailable) return iset(key, val);
return window.localStorage.setItem(fallbackName(key), JSON.stringify(val)); return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val);
} }
export async function del(key: string) { export async function del(key: string) {
if (idbAvailable) return idel(key); if (idbAvailable) return idel(key);
return window.localStorage.removeItem(fallbackName(key)); return miLocalStorage.removeItem(`${PREFIX}${key}`);
} }

View file

@ -5,6 +5,7 @@
export const postMessageEventTypes = [ export const postMessageEventTypes = [
'misskey:shareForm:shareCompleted', 'misskey:shareForm:shareCompleted',
'misskey:embed:changeHeight',
] as const; ] as const;
export type PostMessageEventType = typeof postMessageEventTypes[number]; export type PostMessageEventType = typeof postMessageEventTypes[number];
@ -18,7 +19,7 @@ export type MiPostMessageEvent = {
* *
*/ */
export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void { export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void {
window.postMessage({ window.parent.postMessage({
type, type,
payload, payload,
}, '*'); }, '*');

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="$style.root"> <div :class="isEmbed ? $style.rootForEmbedPage : $style.root">
<div style="container-type: inline-size;"> <div style="container-type: inline-size;">
<RouterView/> <RouterView/>
</div> </div>
@ -20,6 +20,12 @@ import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '
import { instanceName } from '@/config.js'; import { instanceName } from '@/config.js';
import { mainRouter } from '@/router/main.js'; import { mainRouter } from '@/router/main.js';
const props = withDefaults(defineProps<{
isEmbed?: boolean;
}>(), {
isEmbed: false,
});
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index'); const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
const pageMetadata = ref<null | PageMetadata>(null); const pageMetadata = ref<null | PageMetadata>(null);
@ -38,7 +44,9 @@ provideMetadataReceiver((metadataGetter) => {
}); });
provideReactiveMetadata(pageMetadata); provideReactiveMetadata(pageMetadata);
if (!props.isEmbed) {
document.documentElement.style.overflowY = 'scroll'; document.documentElement.style.overflowY = 'scroll';
}
</script> </script>
<style lang="scss" module> <style lang="scss" module>
@ -46,4 +54,8 @@ document.documentElement.style.overflowY = 'scroll';
min-height: 100dvh; min-height: 100dvh;
box-sizing: border-box; box-sizing: border-box;
} }
.rootForEmbedPage {
box-sizing: border-box;
}
</style> </style>