diff --git a/packages/backend/src/core/FanoutTimelineService.ts b/packages/backend/src/core/FanoutTimelineService.ts index aef4ced255..f6dabfadcd 100644 --- a/packages/backend/src/core/FanoutTimelineService.ts +++ b/packages/backend/src/core/FanoutTimelineService.ts @@ -14,9 +14,9 @@ export type FanoutTimelineName = | `homeTimeline:${string}` | `homeTimelineWithFiles:${string}` // only notes with files are included // local timeline - | 'localTimeline' // replies are not included - | 'localTimelineWithFiles' // only non-reply notes with files are included - | 'localTimelineWithReplies' // only replies are included + | `localTimeline` // replies are not included + | `localTimelineWithFiles` // only non-reply notes with files are included + | `localTimelineWithReplies` // only replies are included | `localTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id. // antenna diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index bb658af213..c258a22927 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -12,11 +12,11 @@ import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; -import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; +import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; @Injectable() export class QueueService { diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index e2ec369130..ab75b9abbd 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -31,7 +31,7 @@ export class ApMfmService { const parsed = mfm.parse(srcMfm); - if (!apAppend && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { + if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { noMisskeyContent = true; } diff --git a/packages/backend/src/misc/fastify-hook-handlers.ts b/packages/backend/src/misc/fastify-hook-handlers.ts new file mode 100644 index 0000000000..49a48f6a6b --- /dev/null +++ b/packages/backend/src/misc/fastify-hook-handlers.ts @@ -0,0 +1,9 @@ +import type { onRequestHookHandler } from 'fastify'; + +export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => { + const index = request.url.indexOf('?'); + if (~index) { + reply.redirect(301, request.url.slice(0, index)); + } + done(); +}; diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index 99f4897a1c..408b02fb38 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -34,7 +34,7 @@ export class RelationshipProcessorService { @bindThis public async processFollow(job: Bull.Job): Promise { - this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? 'with replies' : 'without replies'}`); + this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`); await this.userFollowingService.follow(job.data.from, job.data.to, { requestId: job.data.requestId, silent: job.data.silent, diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 7d63cdba05..f51d7aebca 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; import { correctFilename } from '@/misc/correct-filename.js'; +import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; const _filename = fileURLToPath(import.meta.url); @@ -67,20 +68,23 @@ export class FileServerService { done(); }); - fastify.get('/files/app-default.jpg', (request, reply) => { - const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); - reply.header('Content-Type', 'image/jpeg'); - reply.header('Cache-Control', 'max-age=31536000, immutable'); - return reply.send(file); - }); + fastify.register((fastify, options, done) => { + fastify.addHook('onRequest', handleRequestRedirectToOmitSearch); + fastify.get('/files/app-default.jpg', (request, reply) => { + const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); + reply.header('Content-Type', 'image/jpeg'); + reply.header('Cache-Control', 'max-age=31536000, immutable'); + return reply.send(file); + }); - fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => { - return await this.sendDriveFile(request, reply) - .catch(err => this.errorHandler(request, reply, err)); - }); - fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => { - return await this.sendDriveFile(request, reply) - .catch(err => this.errorHandler(request, reply, err)); + fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => { + return await this.sendDriveFile(request, reply) + .catch(err => this.errorHandler(request, reply, err)); + }); + fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => { + return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`); + }); + done(); }); fastify.get<{ diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 058d3d7755..81318ab5ac 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -37,12 +37,12 @@ export class NodeinfoServerService { @bindThis public getLinks() { return [{ - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', - href: this.config.url + nodeinfo2_1path, - }, { - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', - href: this.config.url + nodeinfo2_0path, - }]; + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', + href: this.config.url + nodeinfo2_1path + }, { + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', + href: this.config.url + nodeinfo2_0path, + }]; } @bindThis diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 5a23e8f0e5..2085b06365 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -25,8 +25,8 @@ export const meta = { items: { type: 'object', }, - }, - }, + } + } }, } as const; diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 590fab013f..ceaf32ccb2 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -71,7 +71,7 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id' }, - withReplies: { type: 'boolean' }, + withReplies: { type: 'boolean' } }, required: ['userId'], } as const; diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 8534289a68..30f0c1b0c8 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; -import { safeForSql } from '@/misc/safe-for-sql.js'; +import { safeForSql } from "@/misc/safe-for-sql.js"; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 3f4008a065..d324e3e64a 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -14,7 +14,7 @@ export const meta = { tags: ['account'], requireCredential: true, - kind: 'read:account', + kind: "read:account", res: { type: 'object', diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 8f2fce2b06..efa541b9ab 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -38,9 +38,9 @@ export const meta = { type: 'array', uniqueItems: true, items: { - type: 'string', + type: 'string' }, - }, + } }, }, }, diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index 31c05a5943..dff18d3f45 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -35,7 +35,7 @@ export const meta = { type: 'array', uniqueItems: true, items: { - type: 'string', + type: 'string' }, }, isAuthorized: { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index 46a7321257..ffcf869fcf 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -22,7 +22,7 @@ export const meta = { res: { type: 'object', - }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index 86b0ec4c16..d9a8fdd449 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -22,7 +22,7 @@ export const meta = { res: { type: 'object', - }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts index d1f5df65b0..67a99b028a 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts @@ -22,8 +22,8 @@ export const meta = { type: 'array', items: { type: 'string', - }, - }, + } + } }, domain: { type: 'string', @@ -31,7 +31,7 @@ export const meta = { }, }, }, - }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 198c51b546..c02fad449e 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -33,7 +33,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id', + format: 'misskey:id' }, userId: { type: 'string', @@ -45,7 +45,7 @@ export const meta = { items: { type: 'string', enum: webhookEventTypes, - }, + } }, url: { type: 'string' }, secret: { type: 'string' }, diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index becbf80de1..0e751989bd 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -23,7 +23,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id', + format: 'misskey:id' }, userId: { type: 'string', @@ -35,7 +35,7 @@ export const meta = { items: { type: 'string', enum: webhookEventTypes, - }, + } }, url: { type: 'string' }, secret: { type: 'string' }, @@ -43,8 +43,8 @@ export const meta = { latestSentAt: { type: 'string', format: 'date-time', nullable: true }, latestStatus: { type: 'integer', nullable: true }, }, - }, - }, + } + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index b648b13cb0..f895844e5c 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -30,7 +30,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id', + format: 'misskey:id' }, userId: { type: 'string', @@ -42,7 +42,7 @@ export const meta = { items: { type: 'string', enum: webhookEventTypes, - }, + } }, url: { type: 'string' }, secret: { type: 'string' }, diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 15673a5185..5acc9706d3 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -47,7 +47,7 @@ export const meta = { bothWithRepliesAndWithFiles: { message: 'Specifying both withReplies and withFiles is not supported', code: 'BOTH_WITH_REPLIES_AND_WITH_FILES', - id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f', + id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f' }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 776baed74a..2c801227ca 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -18,7 +18,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id', + format: 'misskey:id' }, required: { type: 'boolean', @@ -34,8 +34,8 @@ export const meta = { default: 'hello', nullable: true, }, - }, - }, + } + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 2592253b8a..f255e28fc2 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -34,6 +34,7 @@ import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { deepClone } from '@/misc/clone.js'; +import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { RoleService } from '@/core/RoleService.js'; @@ -253,11 +254,16 @@ export class ClientServerService { //#region vite assets if (this.config.clientManifestExists) { - fastify.register(fastifyStatic, { - root: viteOut, - prefix: '/vite/', - maxAge: ms('30 days'), - decorateReply: false, + fastify.register((fastify, options, done) => { + fastify.register(fastifyStatic, { + root: viteOut, + prefix: '/vite/', + maxAge: ms('30 days'), + immutable: true, + decorateReply: false, + }); + fastify.addHook('onRequest', handleRequestRedirectToOmitSearch); + done(); }); } else { const port = (process.env.VITE_PORT ?? '5173'); @@ -292,11 +298,16 @@ export class ClientServerService { decorateReply: false, }); - fastify.register(fastifyStatic, { - root: tarball, - prefix: '/tarball/', - immutable: true, - decorateReply: false, + fastify.register((fastify, options, done) => { + fastify.register(fastifyStatic, { + root: tarball, + prefix: '/tarball/', + maxAge: ms('30 days'), + immutable: true, + decorateReply: false, + }); + fastify.addHook('onRequest', handleRequestRedirectToOmitSearch); + done(); }); fastify.get('/favicon.ico', async (request, reply) => { diff --git a/packages/backend/test/e2e/drive.ts b/packages/backend/test/e2e/drive.ts index acffbcd4fc..22ec66e2af 100644 --- a/packages/backend/test/e2e/drive.ts +++ b/packages/backend/test/e2e/drive.ts @@ -7,10 +7,11 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { MiNote } from '@/models/Note.js'; -import type { Packed } from '@/misc/json-schema.js'; import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; -import type{ Repository } from 'typeorm'; +import type{ Repository } from 'typeorm' +import type { Packed } from '@/misc/json-schema.js'; + describe('Drive', () => { let Notes: Repository; @@ -30,7 +31,7 @@ describe('Drive', () => { const marker = Math.random().toString(); - const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'; + const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg' const catcher = makeStreamCatcher( alice, @@ -50,7 +51,7 @@ describe('Drive', () => { assert.strictEqual(res.status, 204); assert.strictEqual(file.name, 'Lenna.jpg'); assert.strictEqual(file.type, 'image/jpeg'); - }); + }) test('ローカルからアップロードできる', async () => { // APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする @@ -58,27 +59,27 @@ describe('Drive', () => { const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' }); assert.strictEqual(res.body?.name, 'テスト画像.jpg'); - assert.strictEqual(res.body.type, 'image/jpeg'); - }); + assert.strictEqual(res.body?.type, 'image/jpeg'); + }) test('添付ノート一覧を取得できる', async () => { - const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id); + const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id) const note0 = await post(alice, { fileIds: [ids[0]] }); const note1 = await post(alice, { fileIds: [ids[0], ids[1]] }); const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice); assert.strictEqual(attached0.body.length, 2); - assert.strictEqual(attached0.body[0].id, note1.id); - assert.strictEqual(attached0.body[1].id, note0.id); + assert.strictEqual(attached0.body[0].id, note1.id) + assert.strictEqual(attached0.body[1].id, note0.id) const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice); assert.strictEqual(attached1.body.length, 1); - assert.strictEqual(attached1.body[0].id, note1.id); + assert.strictEqual(attached1.body[0].id, note1.id) const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice); - assert.strictEqual(attached2.body.length, 0); - }); + assert.strictEqual(attached2.body.length, 0) + }) test('添付ノート一覧は他の人から見えない', async () => { const file = await uploadFile(alice); @@ -88,6 +89,7 @@ describe('Drive', () => { const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob); assert.strictEqual(res.status, 400); assert.strictEqual('error' in res.body, true); - }); + + }) }); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index b1f25a5491..d5da8e0226 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -13,10 +13,10 @@ import fetch, { File, RequestInit } from 'node-fetch'; import { DataSource } from 'typeorm'; import { JSDOM } from 'jsdom'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import { Packed } from '@/misc/json-schema.js'; import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; import type * as misskey from 'misskey-js'; +import { Packed } from '@/misc/json-schema.js'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; @@ -123,9 +123,9 @@ export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', len function timeoutPromise(p: Promise, timeout: number): Promise { return Promise.race([ p, - new Promise((reject) => { - setTimeout(() => { reject(new Error('timed out')); }, timeout); - }) as never, + new Promise((reject) =>{ + setTimeout(() => { reject(new Error('timed out')); }, timeout) + }) as never ]); } @@ -343,7 +343,7 @@ export const uploadUrl = async (user: UserToken, url: string): Promise msg.type === 'urlUploadFinished' && msg.body.marker === marker, (msg) => msg.body.file as Packed<'DriveFile'>, - 60 * 1000, + 60 * 1000 ); await api('drive/files/upload-from-url', { @@ -434,20 +434,20 @@ export const waitFire = async (user: UserToken, channel: string, trgr: () => any * @returns 時間内に正常に処理できた場合に通知からextractorを通した値を得る */ export function makeStreamCatcher( - user: UserToken, - channel: string, - cond: (message: Record) => boolean, - extractor: (message: Record) => T, - timeout = 60 * 1000): Promise { - let ws: WebSocket; + user: UserToken, + channel: string, + cond: (message: Record) => boolean, + extractor: (message: Record) => T, + timeout = 60 * 1000): Promise { + let ws: WebSocket const p = new Promise(async (resolve) => { ws = await connectStream(user, channel, (msg) => { if (cond(msg)) { - resolve(extractor(msg)); + resolve(extractor(msg)) } }); }).finally(() => { - ws.close(); + ws?.close(); }); return timeoutPromise(p, timeout); diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index 2b915f49c2..05c73efe28 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -101,7 +101,7 @@ const announcements = { limit: 10, }; -const isTimelineAvailable = ref(instance.policies.ltlAvailable || instance.policies.gtlAvailable); +const isTimelineAvailable = ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable); const showMenu = ref(false); const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);