diff --git a/.env.mock b/.env.mock index df788721..e45acc79 100644 --- a/.env.mock +++ b/.env.mock @@ -1 +1 @@ -MOCK_USER='{"user":{"server":"universeodon.com","token":"BLMfvYGgiEPgLpiunVS0JYxxqzga3S58C60DDwu1jvw","account":{"id":"109424142224653388","username":"elkdev","acct":"elkdev@universeodon.com","displayName":"Elk Dev Team","locked":false,"bot":false,"discoverable":null,"group":false,"createdAt":"2022-11-28T00:00:00.000Z","note":"","url":"https://universeodon.com/@elkdev","avatar":"https://universeodon.com/avatars/original/missing.png","avatarStatic":"https://universeodon.com/avatars/original/missing.png","header":"https://universeodon.com/headers/original/missing.png","headerStatic":"https://universeodon.com/headers/original/missing.png","followersCount":0,"followingCount":0,"statusesCount":0,"lastStatusAt":null,"noindex":false,"source":{"privacy":"public","sensitive":false,"language":null,"note":"","fields":[],"followRequestsCount":0},"emojis":[],"fields":[],"role":{"id":"-99","name":"","permissions":"65536","color":"","highlighted":false}}},"server":{"109424142224653388":{"uri":"universeodon.com","title":"Universeodon","shortDescription":"Be one with the fediverse.","description":"","email":"novae@universeodon.com","version":"4.0.2","urls":{"streamingApi":"wss://universeodon.com"},"stats":{"userCount":57026,"statusCount":283364,"domainCount":11515},"thumbnail":"https://media.universeodon.com/site_uploads/files/000/000/003/@1x/9de6fc1bbd150b05.png","languages":["en"],"registrations":true,"approvalRequired":false,"invitesEnabled":true,"configuration":{"accounts":{"maxFeaturedTags":10},"statuses":{"maxCharacters":500,"maxMediaAttachments":4,"charactersReservedPerUrl":23},"mediaAttachments":{"supportedMimeTypes":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"imageSizeLimit":10485760,"imageMatrixLimit":16777216,"videoSizeLimit":41943040,"videoFrameRateLimit":60,"videoMatrixLimit":2304000},"polls":{"maxOptions":4,"maxCharactersPerOption":50,"minExpiration":300,"maxExpiration":2629746}},"contactAccount":{"id":"109287809647205395","username":"supernovae","acct":"supernovae","displayName":"Supernovae","locked":false,"bot":false,"discoverable":true,"group":false,"createdAt":"2022-11-04T00:00:00.000Z","note":"","url":"https://universeodon.com/@supernovae","avatar":"https://media.universeodon.com/accounts/avatars/109/287/809/647/205/395/original/551eafba585d19e5.jpg","avatarStatic":"https://media.universeodon.com/accounts/avatars/109/287/809/647/205/395/original/551eafba585d19e5.jpg","header":"https://media.universeodon.com/accounts/headers/109/287/809/647/205/395/original/5de388c5945925c5.jpg","headerStatic":"https://media.universeodon.com/accounts/headers/109/287/809/647/205/395/original/5de388c5945925c5.jpg","followersCount":6387,"followingCount":305,"statusesCount":1753,"lastStatusAt":"2022-11-28","noindex":false,"emojis":[],"fields":[]},"rules":[]}}}' +MOCK_USER='{"user":{"server":"universeodon.com","token":"yZcpj0FmnsEkUvBiXSCb_KQnccl2IU0kx9TfDbcxPJY","vapidKey":"BJwtUVlyCabpMnLI6HOyu-qMfJswxEq_c8pgRymxjTN_vCzMWfGrRHrwNczj9LIokAHtxh6Ziw1Kq7_ERDoriz0=","account":{"id":"109424142224653388","username":"elkdev","acct":"elkdev@universeodon.com","displayName":"Elk Dev Team","locked":false,"bot":false,"discoverable":null,"group":false,"createdAt":"2022-11-28T00:00:00.000Z","note":"","url":"https://universeodon.com/@elkdev","avatar":"https://universeodon.com/avatars/original/missing.png","avatarStatic":"https://universeodon.com/avatars/original/missing.png","header":"https://universeodon.com/headers/original/missing.png","headerStatic":"https://universeodon.com/headers/original/missing.png","followersCount":3,"followingCount":4,"statusesCount":20,"lastStatusAt":"2022-12-13","noindex":false,"source":{"privacy":"public","sensitive":false,"language":null,"note":"","fields":[],"followRequestsCount":0},"emojis":[],"fields":[],"role":{"id":"-99","name":"","permissions":"65536","color":"","highlighted":false}}},"server":{"109424142224653388":{"uri":"universeodon.com","title":"Universeodon","shortDescription":"Be one with the fediverse.","description":"","email":"novae@universeodon.com","version":"4.0.2","urls":{"streamingApi":"wss://universeodon.com"},"stats":{"userCount":57026,"statusCount":283364,"domainCount":11515},"thumbnail":"https://media.universeodon.com/site_uploads/files/000/000/003/@1x/9de6fc1bbd150b05.png","languages":["en"],"registrations":true,"approvalRequired":false,"invitesEnabled":true,"configuration":{"accounts":{"maxFeaturedTags":10},"statuses":{"maxCharacters":500,"maxMediaAttachments":4,"charactersReservedPerUrl":23},"mediaAttachments":{"supportedMimeTypes":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"imageSizeLimit":10485760,"imageMatrixLimit":16777216,"videoSizeLimit":41943040,"videoFrameRateLimit":60,"videoMatrixLimit":2304000},"polls":{"maxOptions":4,"maxCharactersPerOption":50,"minExpiration":300,"maxExpiration":2629746}},"contactAccount":{"id":"109287809647205395","username":"supernovae","acct":"supernovae","displayName":"Supernovae","locked":false,"bot":false,"discoverable":true,"group":false,"createdAt":"2022-11-04T00:00:00.000Z","note":"","url":"https://universeodon.com/@supernovae","avatar":"https://media.universeodon.com/accounts/avatars/109/287/809/647/205/395/original/551eafba585d19e5.jpg","avatarStatic":"https://media.universeodon.com/accounts/avatars/109/287/809/647/205/395/original/551eafba585d19e5.jpg","header":"https://media.universeodon.com/accounts/headers/109/287/809/647/205/395/original/5de388c5945925c5.jpg","headerStatic":"https://media.universeodon.com/accounts/headers/109/287/809/647/205/395/original/5de388c5945925c5.jpg","followersCount":6387,"followingCount":305,"statusesCount":1753,"lastStatusAt":"2022-11-28","noindex":false,"emojis":[],"fields":[]},"rules":[]}}}' diff --git a/.eslintignore b/.eslintignore index d2315970..2c0ecd21 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,5 @@ *.png *.ico *.toml +https-dev-config/localhost.crt +https-dev-config/localhost.key diff --git a/.gitignore b/.gitignore index 4481e661..a48a3cd7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist .DS_Store .idea/ .vite-inspect +.netlify/ public/shiki diff --git a/app.vue b/app.vue index 1dd5617d..b7379cb6 100644 --- a/app.vue +++ b/app.vue @@ -13,4 +13,5 @@ const key = computed(() => `${currentServer.value}:${currentUser.value?.account. + diff --git a/components/PWAPrompt.client.vue b/components/PWAPrompt.client.vue new file mode 100644 index 00000000..056cd2e2 --- /dev/null +++ b/components/PWAPrompt.client.vue @@ -0,0 +1,39 @@ + + + + diff --git a/components/common/CommonCheckbox.vue b/components/common/CommonCheckbox.vue new file mode 100644 index 00000000..a01c3334 --- /dev/null +++ b/components/common/CommonCheckbox.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/components/common/CommonRadio.vue b/components/common/CommonRadio.vue new file mode 100644 index 00000000..2c1e92ef --- /dev/null +++ b/components/common/CommonRadio.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/components/notification/NotificationEnablePushNotification.client.vue b/components/notification/NotificationEnablePushNotification.client.vue new file mode 100644 index 00000000..783970de --- /dev/null +++ b/components/notification/NotificationEnablePushNotification.client.vue @@ -0,0 +1,42 @@ + + + diff --git a/components/notification/NotificationPreferences.client.vue b/components/notification/NotificationPreferences.client.vue new file mode 100644 index 00000000..8e14aefe --- /dev/null +++ b/components/notification/NotificationPreferences.client.vue @@ -0,0 +1,185 @@ + + + diff --git a/composables/push-notifications/createPushSubscription.ts b/composables/push-notifications/createPushSubscription.ts new file mode 100644 index 00000000..61fe0823 --- /dev/null +++ b/composables/push-notifications/createPushSubscription.ts @@ -0,0 +1,119 @@ +import type { + CreatePushSubscriptionParams, + PushSubscription as MastoPushSubscription, +} from 'masto' +import type { + CreatePushNotification, + PushManagerSubscriptionInfo, + RequiredUserLogin, +} from '~/composables/push-notifications/types' +import { useMasto } from '~/composables/masto' +import { currentUser, removePushNotifications } from '~/composables/users' + +export const createPushSubscription = async ( + user: RequiredUserLogin, + notificationData: CreatePushNotification, +): Promise => { + const { server: serverEndpoint, vapidKey } = user + + return await getRegistration() + .then(getPushSubscription) + .then(({ registration, subscription }): Promise => { + if (subscription) { + const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey!)).toString() + const subscriptionServerKey = urlBase64ToUint8Array(vapidKey).toString() + + // If the VAPID public key did not change and the endpoint corresponds + // to the endpoint saved in the backend, the subscription is valid + // If push subscription is not there, we need to create it: it is fetched on login + if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint && user.pushSubscription) { + return Promise.resolve(user.pushSubscription) + } + else if (user.pushSubscription) { + // if we have a subscription, but it is not valid, we need to remove it + return unsubscribeFromBackend(false) + .then(() => subscribe(registration, vapidKey)) + .then(subscription => sendSubscriptionToBackend(subscription, notificationData)) + } + } + + return subscribe(registration, vapidKey).then( + subscription => sendSubscriptionToBackend(subscription, notificationData), + ) + }) + .catch((error) => { + if (error.code === 20 && error.name === 'AbortError') + console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.') + else if (error.code === 5 && error.name === 'InvalidCharacterError') + console.error('The VAPID public key seems to be invalid:', vapidKey) + + return getRegistration() + .then(getPushSubscription) + .then(() => unsubscribeFromBackend(true)) + .then(() => Promise.resolve(undefined)) + .catch((e) => { + console.error(e) + return Promise.resolve(undefined) + }) + }) +} + +// Taken from https://www.npmjs.com/package/web-push +function urlBase64ToUint8Array(base64String: string) { + const padding = '='.repeat((4 - base64String.length % 4) % 4) + const base64 = `${base64String}${padding}` + .replace(/-/g, '+') + .replace(/_/g, '/') + + const rawData = window.atob(base64) + const outputArray = new Uint8Array(rawData.length) + + for (let i = 0; i < rawData.length; ++i) + outputArray[i] = rawData.charCodeAt(i) + + return outputArray +} + +function getRegistration() { + return navigator.serviceWorker.ready +} +async function getPushSubscription(registration: ServiceWorkerRegistration): Promise { + const subscription = await registration.pushManager.getSubscription() + return { registration, subscription } +} + +async function subscribe( + registration: ServiceWorkerRegistration, + applicationServerKey: string, +): Promise { + return await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: urlBase64ToUint8Array(applicationServerKey), + }) +} + +async function unsubscribeFromBackend(fromSWPushManager: boolean) { + const cu = currentUser.value + if (cu) + await removePushNotifications(cu, fromSWPushManager) +} + +async function sendSubscriptionToBackend( + subscription: PushSubscription, + data: CreatePushNotification, +): Promise { + const { endpoint, keys } = subscription.toJSON() + const params: CreatePushSubscriptionParams = { + subscription: { + endpoint: endpoint!, + keys: { + p256dh: keys!.p256dh!, + auth: keys!.auth!, + }, + }, + data, + } + + return await useMasto().pushSubscriptions.create(params) +} + diff --git a/composables/push-notifications/types.ts b/composables/push-notifications/types.ts new file mode 100644 index 00000000..d3b617c2 --- /dev/null +++ b/composables/push-notifications/types.ts @@ -0,0 +1,22 @@ +import type { PushSubscription as MastoPushSubscription, PushSubscriptionAlerts, SubscriptionPolicy } from 'masto' + +import type { UserLogin } from '~/types' + +export type SubscriptionResult = 'subscribed' | 'notification-denied' | 'invalid-state' +export interface PushManagerSubscriptionInfo { + registration: ServiceWorkerRegistration + subscription: PushSubscription | null +} + +export interface RequiredUserLogin extends Required> { + pushSubscription?: MastoPushSubscription +} + +export interface CreatePushNotification { + alerts?: Partial | null + policy?: SubscriptionPolicy +} + +export type PushNotificationRequest = Record +export type PushNotificationPolicy = Record + diff --git a/composables/push-notifications/usePushManager.ts b/composables/push-notifications/usePushManager.ts new file mode 100644 index 00000000..057a31c3 --- /dev/null +++ b/composables/push-notifications/usePushManager.ts @@ -0,0 +1,171 @@ +import type { + CreatePushNotification, + PushNotificationPolicy, + PushNotificationRequest, + SubscriptionResult, +} from '~/composables/push-notifications/types' +import { createPushSubscription } from '~/composables/push-notifications/createPushSubscription' +import { STORAGE_KEY_NOTIFICATION, STORAGE_KEY_NOTIFICATION_POLICY } from '~/constants' +import { currentUser, removePushNotifications } from '~/composables/users' + +const supportsPushNotifications = typeof window !== 'undefined' + && 'serviceWorker' in navigator + && 'PushManager' in window + && 'getKey' in PushSubscription.prototype + +export const usePushManager = () => { + const isSubscribed = ref(false) + const notificationPermission = ref( + Notification.permission === 'denied' + ? 'denied' + : Notification.permission === 'granted' + ? 'granted' + : Notification.permission === 'default' + ? 'prompt' + : undefined, + ) + const isSupported = $computed(() => supportsPushNotifications) + const hiddenNotification = useLocalStorage(STORAGE_KEY_NOTIFICATION, {}) + const configuredPolicy = useLocalStorage(STORAGE_KEY_NOTIFICATION_POLICY, {}) + const pushNotificationData = ref({ + follow: currentUser.value?.pushSubscription?.alerts.follow ?? true, + favourite: currentUser.value?.pushSubscription?.alerts.favourite ?? true, + reblog: currentUser.value?.pushSubscription?.alerts.reblog ?? true, + mention: currentUser.value?.pushSubscription?.alerts.mention ?? true, + poll: currentUser.value?.pushSubscription?.alerts.poll ?? true, + policy: configuredPolicy.value[currentUser.value?.account?.acct ?? ''] ?? 'all', + }) + const { history, commit, clear } = useManualRefHistory(pushNotificationData, { clone: true }) + const saveEnabled = computed(() => { + const current = pushNotificationData.value + const previous = history.value?.[0]?.snapshot + return current.favourite !== previous.favourite + || current.reblog !== previous.reblog + || current.mention !== previous.mention + || current.follow !== previous.follow + || current.poll !== previous.poll + || current.policy !== previous.policy + }) + + watch(() => currentUser.value?.pushSubscription, (subscription) => { + isSubscribed.value = !!subscription + pushNotificationData.value = { + follow: subscription?.alerts.follow ?? false, + favourite: subscription?.alerts.favourite ?? false, + reblog: subscription?.alerts.reblog ?? false, + mention: subscription?.alerts.mention ?? false, + poll: subscription?.alerts.poll ?? false, + policy: configuredPolicy.value[currentUser.value?.account?.acct ?? ''] ?? 'all', + } + }, { immediate: true, flush: 'post' }) + + const subscribe = async (notificationData?: CreatePushNotification): Promise => { + if (!isSupported || !currentUser.value) + return 'invalid-state' + + const { pushSubscription, server, token, vapidKey, account: { acct } } = currentUser.value + + if (!token || !server || !vapidKey) + return 'invalid-state' + + let permission: PermissionState | undefined + + if (!notificationPermission.value || (notificationPermission.value === 'prompt' && !hiddenNotification.value[acct])) { + // safari 16 does not support navigator.permissions.query for notifications + try { + permission = (await navigator.permissions?.query({ name: 'notifications' }))?.state + } + catch { + permission = await Promise.resolve(Notification.requestPermission()).then((p: NotificationPermission) => { + return p === 'default' ? 'prompt' : p + }) + } + } + else { + permission = notificationPermission.value + } + + if (!permission || permission === 'denied') { + notificationPermission.value = permission + return 'notification-denied' + } + + currentUser.value.pushSubscription = await createPushSubscription({ + pushSubscription, server, token, vapidKey, + }, notificationData ?? { + alerts: { + follow: true, + favourite: true, + reblog: true, + mention: true, + poll: true, + }, + policy: 'all', + }) + await nextTick() + notificationPermission.value = permission + hiddenNotification.value[acct] = true + + return 'subscribed' + } + + const unsubscribe = async () => { + if (!isSupported || !isSubscribed || !currentUser.value) + return false + + await removePushNotifications(currentUser.value) + } + + const saveSettings = async () => { + commit() + configuredPolicy.value[currentUser.value!.account.acct ?? ''] = pushNotificationData.value.policy + await nextTick() + clear() + await nextTick() + } + + const undoChanges = () => { + const current = pushNotificationData.value + const previous = history.value[0].snapshot + current.favourite = previous.favourite + current.reblog = previous.reblog + current.mention = previous.mention + current.follow = previous.follow + current.poll = previous.poll + current.policy = previous.policy + configuredPolicy.value[currentUser.value!.account.acct ?? ''] = previous.policy + commit() + clear() + } + + const updateSubscription = async () => { + if (currentUser.value) { + currentUser.value.pushSubscription = await useMasto().pushSubscriptions.update({ + data: { + alerts: { + follow: pushNotificationData.value.follow, + favourite: pushNotificationData.value.favourite, + reblog: pushNotificationData.value.reblog, + mention: pushNotificationData.value.mention, + poll: pushNotificationData.value.poll, + }, + policy: pushNotificationData.value.policy, + }, + }) + await saveSettings() + } + } + + return { + pushNotificationData, + saveEnabled, + undoChanges, + hiddenNotification, + isSupported, + isSubscribed, + notificationPermission, + updateSubscription, + subscribe, + unsubscribe, + } +} diff --git a/composables/pwa/index.ts b/composables/pwa/index.ts new file mode 100644 index 00000000..09878d11 --- /dev/null +++ b/composables/pwa/index.ts @@ -0,0 +1,42 @@ +import { useRegisterSW } from 'virtual:pwa-register/vue' + +export const usePWA = () => { + const online = useOnline() + + const { + needRefresh, + updateServiceWorker, + } = useRegisterSW({ + immediate: true, + onRegisteredSW(swUrl, r) { + if (!r || r.installing) + return + + setInterval(async () => { + if (!online.value) + return + + const resp = await fetch(swUrl, { + cache: 'no-store', + headers: { + 'cache': 'no-store', + 'cache-control': 'no-cache', + }, + }) + + if (resp?.status === 200) + await r.update() + }, 60 * 60 * 1000 /* 1 hour */) + }, + }) + + const close = async () => { + needRefresh.value = false + } + + return { + needRefresh, + updateServiceWorker, + close, + } +} diff --git a/composables/setups.ts b/composables/setups.ts index 9f976243..4c2a9304 100644 --- a/composables/setups.ts +++ b/composables/setups.ts @@ -1,3 +1,5 @@ +import { pwaInfo } from 'virtual:pwa-info' +import type { Link } from '@unhead/schema' import { APP_NAME, STORAGE_KEY_LANG } from '~/constants' export function setupPageHeader() { @@ -6,11 +8,34 @@ export function setupPageHeader() { const i18n = useI18n() + const link: Link[] = [] + + if (pwaInfo && pwaInfo.webManifest) { + const { webManifest } = pwaInfo + if (webManifest) { + const { href, useCredentials } = webManifest + if (useCredentials) { + link.push({ + rel: 'manifest', + href, + crossorigin: 'use-credentials', + }) + } + else { + link.push({ + rel: 'manifest', + href, + }) + } + } + } + useHeadFixed({ htmlAttrs: { lang: () => i18n.locale.value, }, titleTemplate: title => `${title ? `${title} | ` : ''}${APP_NAME}${isDev ? ' (dev)' : isPreview ? ' (preview)' : ''}`, + link, }) // eslint-disable-next-line no-unused-expressions diff --git a/composables/users.ts b/composables/users.ts index f4dc1f30..b59a93be 100644 --- a/composables/users.ts +++ b/composables/users.ts @@ -2,7 +2,16 @@ import { login as loginMasto } from 'masto' import type { Account, AccountCredentials, Instance, WsEvents } from 'masto' import type { Ref } from 'vue' import type { UserLogin } from '~/types' -import { DEFAULT_POST_CHARS_LIMIT, DEFAULT_SERVER, STORAGE_KEY_CURRENT_USER, STORAGE_KEY_SERVERS, STORAGE_KEY_USERS } from '~/constants' +import { + DEFAULT_POST_CHARS_LIMIT, + DEFAULT_SERVER, + STORAGE_KEY_CURRENT_USER, + STORAGE_KEY_NOTIFICATION, + STORAGE_KEY_NOTIFICATION_POLICY, + STORAGE_KEY_SERVERS, + STORAGE_KEY_USERS, +} from '~/constants' +import type { PushNotificationPolicy, PushNotificationRequest } from '~/composables/push-notifications/types' const mock = process.mock const users = useLocalStorage(STORAGE_KEY_USERS, mock ? [mock.user] : [], { deep: true }) @@ -53,12 +62,15 @@ export async function loginTo(user?: Omit & { account?: Ac else { try { - const [me, server] = await Promise.all([ + const [me, server, pushSubscription] = await Promise.all([ masto.accounts.verifyCredentials(), masto.instances.fetch(), + // we get 404 response instead empty data + masto.pushSubscriptions.fetch().catch(() => Promise.resolve(undefined)), ]) user.account = me + user.pushSubscription = pushSubscription currentUserId.value = me.id servers.value[me.id] = server @@ -83,6 +95,37 @@ export async function loginTo(user?: Omit & { account?: Ac return masto } +export async function removePushNotifications(user: UserLogin, fromSWPushManager = true) { + // unsubscribe push notifications + try { + await useMasto().pushSubscriptions.remove() + } + catch { + // ignore + } + // clear push subscription + user.pushSubscription = undefined + const { acct } = user.account + // clear request notification permission + delete useLocalStorage(STORAGE_KEY_NOTIFICATION, {}).value[acct] + // clear push notification policy + delete useLocalStorage(STORAGE_KEY_NOTIFICATION_POLICY, {}).value[acct] + + // we remove the sw push manager if required and there are no more accounts with subscriptions + if (fromSWPushManager && (users.value.length === 0 || users.value.every(u => !u.pushSubscription))) { + // clear sw push subscription + try { + const registration = await navigator.serviceWorker.ready + const subscription = await registration.pushManager.getSubscription() + if (subscription) + await subscription.unsubscribe() + } + catch { + // juts ignore + } + } +} + export async function signout() { // TODO: confirm if (!currentUser.value) @@ -97,6 +140,8 @@ export async function signout() { clearUserLocalStorage() delete servers.value[_currentUserId] + await removePushNotifications(currentUser.value) + currentUserId.value = '' // Remove the current user from the users users.value.splice(index, 1) diff --git a/config/pwa.ts b/config/pwa.ts new file mode 100644 index 00000000..5f288d1e --- /dev/null +++ b/config/pwa.ts @@ -0,0 +1,51 @@ +import { isCI } from 'std-env' +import type { VitePWANuxtOptions } from '../modules/pwa/types' + +const isPreview = process.env.PULL_REQUEST === 'true' + +const pwa: VitePWANuxtOptions = { + mode: isCI ? 'production' : 'development', + // disabled PWA only on production + disable: !isPreview && process.env.VITE_DEV_PWA !== 'true', + scope: '/', + srcDir: './service-worker', + filename: 'sw.ts', + strategies: 'injectManifest', + injectRegister: false, + includeManifestIcons: false, + manifest: { + scope: '/', + id: '/', + name: `Elk${isCI ? isPreview ? ' (preview)' : '' : ' (dev)'}`, + short_name: `Elk${isCI ? isPreview ? ' (preview)' : '' : ' (dev)'}`, + description: `A nimble Mastodon Web Client${isCI ? isPreview ? ' (preview)' : '' : ' (development)'}`, + theme_color: '#ffffff', + icons: [ + { + src: 'pwa-192x192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: 'pwa-512x512.png', + sizes: '512x512', + type: 'image/png', + }, + { + src: 'logo.svg', + sizes: '250x250', + type: 'image/png', + purpose: 'any maskable', + }, + ], + }, + injectManifest: { + globPatterns: ['**/*.{js,json,css,html,txt,svg,png,ico,webp,woff,woff2,ttf,eot,otf,wasm}'], + }, + devOptions: { + enabled: process.env.VITE_DEV_PWA === 'true', + type: 'module', + }, +} + +export { pwa } diff --git a/constants/index.ts b/constants/index.ts index 676e6cb1..9b9e225c 100644 --- a/constants/index.ts +++ b/constants/index.ts @@ -15,5 +15,7 @@ export const STORAGE_KEY_FEATURE_FLAGS = 'elk-feature-flags' export const STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS = 'elk-hide-explore-posts-tips' export const STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS = 'elk-hide-explore-news-tips' export const STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS = 'elk-hide-explore-tags-tips' +export const STORAGE_KEY_NOTIFICATION = 'elk-notification' +export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy' export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/ diff --git a/error.vue b/error.vue index 7ea016fc..c4b9ee86 100644 --- a/error.vue +++ b/error.vue @@ -54,4 +54,5 @@ const reload = async () => { + diff --git a/https-dev-config/local-https-server.mjs b/https-dev-config/local-https-server.mjs new file mode 100644 index 00000000..6ea2469a --- /dev/null +++ b/https-dev-config/local-https-server.mjs @@ -0,0 +1,12 @@ +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' + +process.env.NITRO_SSL_CERT = readFileSync(fileURLToPath(new URL('./localhost.crt', import.meta.url)), 'utf8') +process.env.NITRO_SSL_KEY = readFileSync(fileURLToPath(new URL('./localhost.key', import.meta.url)), 'utf8') + +async function run() { + await import('../.output/server/index.mjs') +} + +run() + diff --git a/https-dev-config/localhost.crt b/https-dev-config/localhost.crt new file mode 100644 index 00000000..483798f4 --- /dev/null +++ b/https-dev-config/localhost.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIELjCCApagAwIBAgIRAKdt7EA97mviRgEBUUnM2LAwDQYJKoZIhvcNAQELBQAw +dTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSUwIwYDVQQLDBxCTEFD +S1JPQ0tcSm9hcXXDrW5AQmxhY2tSb2NrMSwwKgYDVQQDDCNta2NlcnQgQkxBQ0tS +T0NLXEpvYXF1w61uQEJsYWNrUm9jazAeFw0yMjA4MzAyMTQxNTRaFw0yNDExMzAy +MjQxNTRaMFAxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0 +ZTElMCMGA1UECwwcQkxBQ0tST0NLXEpvYXF1w61uQEJsYWNrUm9jazCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAPepkg2Nec3FUxqNfrq/8pHXL88G2Bsn +Oyy2bJ1D3k9/7Mn5RkZ67dCs9XVa4u5gtkGnMy+S5FqGyhahEaaW6k45Vbs8uIgE +1i8tx90r6rtIqXedkJyrhdr5xZWNzzj2ItmFkU1KGnCbFj8ZgXLW2miqXbWgpLLe +eRTOIadcQJlQJC5LTAIzOSsZyWvrQw2UaOjAqrSdFbXm0/G/V6MFBlsat6MgsFDg +8JuvITDYX6dX0jhtO5mQvJRESkP/5TaOdxzxjjTnXrTEIYn+QJUJ+rwa2d9fv7pM +CdQ0kHBezYKzp2NMKp7rpIQxFbFzR9NADk8wLaAQMUBz5QS435Q9998CAwEAAaNe +MFwwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQY +MBaAFAnP38cmUbpd5YTbKDLGa6I2FkzyMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAN +BgkqhkiG9w0BAQsFAAOCAYEAUIQp0DtACRVAAat4Sl1kzbOI35aIQjkSsX4KgIgC +8HX3qFa4NpbOBmshvgAZFrNQzJS/dLz3oOg7Ww/UH6BZjQT5QCHK7ASA12Ick1Iz +V3aTicaXn7ZyHMOpJwJXgwh6Ekv/sNjr7Sc0NahisRkAR1KglymQtYm3bGbFEKzW +0pyGnDvDBmLuQCfxq2ZwnX7eqM9R3BXBVRFe3uRoqIdwHorlupZ8N+rfyumJjIUP +gOWDUf3VuqnqhjK0BMDdTEkF8po9YQ5Qtj5Iw0JSSBfWE4WJsqyC+4EueCCHuNYs +rTtItjGT6WNGnEGI8VNijtmwHL1cPDmE6l+YrnCK0JG0u21Y+osWMeRhJhGAPY4d +cIu15gcm/PRG0hdYZrGuz18hKNy9NRtIK+na8k2R6o+uNDekIB7Pk6DHoA8Z6UDU +Q5Au+2FAt6mMyFNcifj965nPAnnSv1hg45fFwIww8edClXqfB6MCNxFO6Yzfn2UD +ABNN450+8Nd1pgXJEifiDAIe +-----END CERTIFICATE----- diff --git a/https-dev-config/localhost.key b/https-dev-config/localhost.key new file mode 100644 index 00000000..3d9d8fcd --- /dev/null +++ b/https-dev-config/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3qZINjXnNxVMa +jX66v/KR1y/PBtgbJzsstmydQ95Pf+zJ+UZGeu3QrPV1WuLuYLZBpzMvkuRahsoW +oRGmlupOOVW7PLiIBNYvLcfdK+q7SKl3nZCcq4Xa+cWVjc849iLZhZFNShpwmxY/ +GYFy1tpoql21oKSy3nkUziGnXECZUCQuS0wCMzkrGclr60MNlGjowKq0nRW15tPx +v1ejBQZbGrejILBQ4PCbryEw2F+nV9I4bTuZkLyUREpD/+U2jncc8Y405160xCGJ +/kCVCfq8GtnfX7+6TAnUNJBwXs2Cs6djTCqe66SEMRWxc0fTQA5PMC2gEDFAc+UE +uN+UPfffAgMBAAECggEBAO84XLo4iInI6x+/wsSSObTDXQuk+cMontDumHVDpA24 +bDkfTdEwVlv1ZNbpZj+JLSK3ZQqz4VzLy5IWHJ2EMmhCm1vTKA9CVLyXhPFOxVoH +sqG2kYOzbgT4s/BkXOARZ9IiYRp91JImS1PByDbr72WgAgo5VDzuBZiiDwHAaylp +yDwwvPPtps6cP7k30RW7l3W2CmL2p8cta9g8NfNBmq6NHDjwsqvxzPNZ2O2S+V70 +55DBH2yNtHa7lwDC9jcwULhtGk2k9kqwfyd9c+QxeIxX/sA7xDJSmK72yutTT+Hw +F5Cttw6aifcRQURoLQ6Qwm1iP93rKN/FRxqGlCFpDMECgYEA/4gL0/m59gaCjShK +SyfaTkGzqJWVNEiHq8Qc9/2DQ4cl0u5aKI6H7wBRiTXfpeYmWmDjGGAEl0UlxtFu +c7OREA47wbgNc4cBvgrrslUzyduV+0CIRWypxgiT+KTl7T2lVy5VreYlr4gaM0rt +68N3jbktyM1R57aIs5XJODAtZWECgYEA+B3UkndgAyk8tUrZaOn7jE4ZnwrLUnRw +nGbAiZG3lmZEULO8jBFJFM2oeuj6467+ckZRviIVWQ8T2KqMye8eB7+fHOBOKCXs +PsCUV/asqN9ibh9UumOKkAsnO1G4p1+EJHzYNNucO3dEF2QTmEQvyHnw4tvxgos5 +bf1YJjZLJT8CgYEAwDkDTM6LCXwUMUOhv6+XFU9vat47gz0cciXw9MyMNfwwg+Ax +iljOAQhoTaNtPktHhq1jqC5yxaiKpmldgUQPV9idMzjVRZbFxMRKUbiuYKcCyCLf +X/pCLGq/hUfmfvTksBR2934t00G7E+LF35kHEmG/A1MQzhIN+6ot2ErFm4ECgYAH +o9OB1w8ryb9GzdE3+8x1G4qKbSipl1BIYJmZItWGWgvMeFxb68RWUabYcggXrrHD +DwtBUYdawK4Zw9al+SjxkCL0HqwJbHGD1SY8NypF4OsE/Q3810fS+6TvnKqU7MoC +3Z1Cs2hyJFACcGByFddq0uZp9d/P5z2Td3OZaZ6SvQKBgEQ754VsRiO9zYR70kXf +5ZG75rZICgu14fHxwStCWghvD/4AT53y6kQ1gpdTEKspCmYv3f5xbOPVIW4agHEw +DHZK7EsLEW1EmXdA9QIBKgcBqaGEzuKVtWFzCgNupssC8N9ys3X8r2nNM4qeXHsz +t1+EzpMzGsmMWuJ5l0TcI+W+ +-----END PRIVATE KEY----- diff --git a/locales/en-US.json b/locales/en-US.json index 6d3b0380..f708e3ce 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -144,6 +144,38 @@ "missing_type": "MISSING notification.type:", "reblogged_post": "reblogged your post", "request_to_follow": "requested to follow you", + "settings": { + "alerts": { + "favourite": "Favourites", + "follow": "New followers", + "mention": "Mentions", + "poll": "Polls", + "reblog": "Reblog your post", + "title": "What notifications to receive?" + }, + "close_btn": "Close desktop notification settings", + "policy": { + "all": "From anyone", + "followed": "Of people I follow", + "follower": "Of people who follow me", + "none": "From no one", + "title": "Who can I receive notifications from?" + }, + "save_settings": "Save settings changes", + "show_btn": "Show desktop notification settings", + "title": "Desktop notification settings", + "undo_settings": "Undo settings changes", + "unsubscribe": "Disable desktop notifications", + "unsubscribed_with_warning": "Enable notifications to receive notifications from this account by clicking \"@:notification.settings.warning.enable_desktop{'\"'} button.", + "unsupported": "Your browser does not support desktop notifications.", + "warning": { + "enable_close": "Close", + "enable_description": "To receive notifications when Elk is not open, enable desktop notifications. You can control precisely what types of interactions generate desktop notifications via the \"Show Settings\" button above once enabled.", + "enable_description_short": "To change desktop notification settings when Elk is not open, you must first enable desktop notifications.", + "enable_desktop": "Enable desktop notifications", + "enable_title": "Never miss anything" + } + }, "update_status": "updated their status" }, "placeholder": { @@ -153,6 +185,12 @@ "replying": "Replying", "the_thread": "the thread" }, + "pwa": { + "close": "Close", + "message": "@:pwa.title{','} click on @:pwa.reload button to update.", + "reload": "Reload", + "title": "New Elk version available" + }, "search": { "search_desc": "Search for people & hashtags" }, diff --git a/locales/es-ES.json b/locales/es-ES.json index e92b7045..88bcc815 100644 --- a/locales/es-ES.json +++ b/locales/es-ES.json @@ -141,6 +141,38 @@ "missing_type": "MISSING notification.type:", "reblogged_post": "retooteó tu publicación", "request_to_follow": "ha solicitado seguirte", + "settings": { + "alerts": { + "favourite": "Favoritos", + "follow": "Nuevos seguidores", + "mention": "Menciones", + "poll": "Encuestas", + "reblog": "Retooteo de tus publicaciones", + "title": "¿Qué notificaciones recibir?" + }, + "close_btn": "Cerrar ajuste de las notificaciones de escritorio", + "policy": { + "all": "De cualquier persona", + "followed": "De personas que sigo", + "follower": "De personas que me siguen", + "none": "De nadie", + "title": "¿De quién puedo recibir notificaciones?" + }, + "save_settings": "Guardar cambios en los ajustes", + "show_btn": "Mostrar ajustes de las notificaciones de escritorio", + "title": "Ajustes de notificaciones de escritorio", + "undo_settings": "Deshacer cambios en los ajustes", + "unsubscribe": "Cancelar notificaciones de escritorio", + "unsubscribed_with_warning": "Habilite las notificaciones para recibir notificaciones de esta cuenta haciendo clic en el botón \"@:notification.settings.warning.enable_desktop{'\"'}.", + "unsupported": "Tu navegador no soporta notificaciones de escritorio.", + "warning": { + "enable_close": "Cerrar", + "enable_description": "Para recibir notificaciones cuando Elk no esté abierto, habilite las notificaciones de escritorio. Puedes controlar con precisión qué tipos de interacciones generan notificaciones de escritorio a través del botón \"Mostrar ajustes\" de arriba una vez que estén habilitadas.", + "enable_description_short": "Para cambiar los ajustes de las notificaciones de escritorio cuando Elk no esté abierto, debe habilitar antes las notificaciones de escritorio.", + "enable_desktop": "Habilitar notificaciones de escritorio", + "enable_title": "Nunca te pierdas nada" + } + }, "update_status": "ha actualizado su estado" }, "placeholder": { @@ -150,6 +182,12 @@ "replying": "Respondiendo", "the_thread": "el hilo" }, + "pwa": { + "close": "Cerrar", + "message": "@:pwa.title{','} haz click en el botón @:pwa.reload para actualizar.", + "reload": "Recargar", + "title": "Nueva versión de Elk disponible" + }, "state": { "edited": "(Editado)", "editing": "Editando", diff --git a/modules/pwa/config.ts b/modules/pwa/config.ts new file mode 100644 index 00000000..262f00ab --- /dev/null +++ b/modules/pwa/config.ts @@ -0,0 +1,69 @@ +import type { Nuxt } from '@nuxt/schema' +import type { VitePWAOptions } from 'vite-plugin-pwa' +import { resolve } from 'pathe' + +export function configurePWAOptions(options: Partial, nuxt: Nuxt) { + if (!options.outDir) { + const publicDir = nuxt.options.nitro?.output?.publicDir + options.outDir = publicDir ? resolve(publicDir) : resolve(nuxt.options.buildDir, '../.output/public') + } + + let config: Partial< + import('workbox-build').BasePartial + & import('workbox-build').GlobPartial + & import('workbox-build').RequiredGlobDirectoryPartial + > + + if (options.strategies === 'injectManifest') { + options.injectManifest = options.injectManifest ?? {} + config = options.injectManifest + } + else { + options.workbox = options.workbox ?? {} + if (options.registerType === 'autoUpdate' && (options.injectRegister === 'script' || options.injectRegister === 'inline')) { + options.workbox.clientsClaim = true + options.workbox.skipWaiting = true + } + if (nuxt.options.dev) { + // on dev force always to use the root + + options.workbox.navigateFallback = nuxt.options.app.baseURL ?? '/' + if (options.devOptions?.enabled && !options.devOptions.navigateFallbackAllowlist) + options.devOptions.navigateFallbackAllowlist = [new RegExp(nuxt.options.app.baseURL) ?? /\//] + } + config = options.workbox + // todo: change navigateFallback based on the command: use 404 only when using generate + /* else if (nuxt.options.build) { + if (!options.workbox.navigateFallback) + options.workbox.navigateFallback = '/200.html' + } */ + } + if (!nuxt.options.dev) + config.manifestTransforms = [createManifestTransform(nuxt.options.app.baseURL ?? '/')] +} + +function createManifestTransform(base: string): import('workbox-build').ManifestTransform { + return async (entries) => { + // prefix non html assets with base + /* + entries.filter(e => e && !e.url.endsWith('.html')).forEach((e) => { + if (!e.url.startsWith(base)) + e.url = `${base}${e.url}` + }) +*/ + entries.filter(e => e && e.url.endsWith('.html')).forEach((e) => { + const url = e.url.startsWith('/') ? e.url.slice(1) : e.url + if (url === 'index.html') { + e.url = base + } + else { + const parts = url.split('/') + parts[parts.length - 1] = parts[parts.length - 1].replace(/\.html$/, '') + // e.url = `${base}${parts.length > 1 ? parts.slice(0, parts.length - 1).join('/') : parts[0]}` + e.url = parts.length > 1 ? parts.slice(0, parts.length - 1).join('/') : parts[0] + } + }) + + return { manifest: entries, warnings: [] } + } +} diff --git a/modules/pwa/index.ts b/modules/pwa/index.ts new file mode 100644 index 00000000..40d34357 --- /dev/null +++ b/modules/pwa/index.ts @@ -0,0 +1,83 @@ +import { defineNuxtModule } from '@nuxt/kit' +import type { VitePluginPWAAPI } from 'vite-plugin-pwa' +import { VitePWA } from 'vite-plugin-pwa' +import type { Plugin } from 'vite' +import type { VitePWANuxtOptions } from './types' +import { configurePWAOptions } from './config' + +export * from './types' +export default defineNuxtModule({ + meta: { + name: 'pwa', + configKey: 'pwa', + }, + defaults: nuxt => ({ + base: nuxt.options.app.baseURL, + scope: nuxt.options.app.baseURL, + }), + async setup(options, nuxt) { + let vitePwaClientPlugin: Plugin | undefined + const resolveVitePluginPWAAPI = (): VitePluginPWAAPI | undefined => { + return vitePwaClientPlugin?.api + } + + // TODO: combine with configurePWAOptions? + nuxt.hook('nitro:init', (nitro) => { + options.outDir = nitro.options.output.publicDir + options.injectManifest = options.injectManifest || {} + options.injectManifest.globDirectory = nitro.options.output.publicDir + }) + nuxt.hook('vite:extend', ({ config }) => { + const plugin = config.plugins?.find(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite-plugin-pwa') + if (plugin) + throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!') + }) + nuxt.hook('vite:extendConfig', (viteInlineConfig, { isClient }) => { + viteInlineConfig.plugins = viteInlineConfig.plugins || [] + const plugin = viteInlineConfig.plugins.find(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite-plugin-pwa') + if (plugin) + throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!') + + configurePWAOptions(options, nuxt) + const plugins = VitePWA(options) + viteInlineConfig.plugins.push(plugins) + if (isClient) + vitePwaClientPlugin = plugins.find(p => p.name === 'vite-plugin-pwa') as Plugin + }) + + if (nuxt.options.dev) { + const webManifest = `${nuxt.options.app.baseURL}${options.devOptions?.webManifestUrl ?? options.manifestFilename ?? 'manifest.webmanifest'}` + const devSw = `${nuxt.options.app.baseURL}dev-sw.js?dev-sw` + const workbox = `${nuxt.options.app.baseURL}workbox-` + // @ts-expect-error just ignore + const emptyHandle = (_req, _res, next) => { + next() + } + nuxt.hook('vite:serverCreated', (viteServer, { isServer }) => { + if (isServer) + return + + viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle }) + viteServer.middlewares.stack.push({ route: devSw, handle: emptyHandle }) + }) + if (!options.strategies || options.strategies === 'generateSW') { + nuxt.hook('vite:serverCreated', (viteServer, { isServer }) => { + if (isServer) + return + + viteServer.middlewares.stack.push({ route: workbox, handle: emptyHandle }) + }) + nuxt.hook('close', async () => { + // todo: cleanup dev-dist folder + }) + } + } + else { + nuxt.hook('nitro:init', (nitro) => { + nitro.hooks.hook('rollup:before', async () => { + await resolveVitePluginPWAAPI()?.generateSW() + }) + }) + } + }, +}) diff --git a/modules/pwa/types.ts b/modules/pwa/types.ts new file mode 100644 index 00000000..90446c52 --- /dev/null +++ b/modules/pwa/types.ts @@ -0,0 +1,12 @@ +import type { VitePWAOptions } from 'vite-plugin-pwa' + +export interface VitePWANuxtOptions extends Partial {} + +declare module '@nuxt/schema' { + interface NuxtConfig { + pwa?: { [K in keyof VitePWANuxtOptions]?: Partial } + } + interface NuxtOptions { + pwa: VitePWANuxtOptions + } +} diff --git a/netlify.toml b/netlify.toml index d6f1f50a..9060086f 100755 --- a/netlify.toml +++ b/netlify.toml @@ -8,8 +8,3 @@ to = "https://discord.gg/vAZSDU9J" status = 301 force = true - -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 diff --git a/nuxt.config.ts b/nuxt.config.ts index ac7704d6..a28dae25 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -2,10 +2,16 @@ import { fileURLToPath } from 'node:url' import Inspect from 'vite-plugin-inspect' import { isCI } from 'std-env' import { i18n } from './config/i18n' +import { pwa } from './config/pwa' const isPreview = process.env.PULL_REQUEST === 'true' export default defineNuxtConfig({ + typescript: { + tsConfig: { + exclude: ['../service-worker'], + }, + }, modules: [ '@vueuse/nuxt', '@unocss/nuxt', @@ -14,6 +20,7 @@ export default defineNuxtConfig({ '@nuxtjs/i18n', '~/modules/purge-comments', '~/modules/setup-components', + '~/modules/pwa/index', // change to '@vite-pwa/nuxt' once released and remove pwa module ], experimental: { reactivityTransform: true, @@ -66,6 +73,7 @@ export default defineNuxtConfig({ }, public: { env: isCI ? isPreview ? 'staging' : 'production' : 'local', + pwaEnabled: isCI || process.env.VITE_DEV_PWA === 'true', translateApi: '', // Masto uses Mastodon version checks to see what features are enabled. // Mastodon alternatives like GoToSocial will always fail these checks, so @@ -77,6 +85,13 @@ export default defineNuxtConfig({ fsBase: 'node_modules/.cache/servers', }, }, + routeRules: { + '/manifest.webmanifest': { + headers: { + 'Content-Type': 'application/manifest+json', + }, + }, + }, nitro: { publicAssets: [ ...(!isCI || isPreview ? [{ dir: fileURLToPath(new URL('./public-dev', import.meta.url)) }] : []), @@ -99,10 +114,14 @@ export default defineNuxtConfig({ { rel: 'alternate icon', type: 'image/x-icon', href: '/favicon.ico' }, { rel: 'icon', type: 'image/png', href: '/favicon-16x16.png', sizes: '16x16' }, { rel: 'icon', type: 'image/png', href: '/favicon-32x32.png', sizes: '32x32' }, + { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#ffffff' }, + { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }, ], + meta: [{ name: 'theme-color', content: '#ffffff' }], }, }, i18n, + pwa, }) declare global { diff --git a/package.json b/package.json index 1fc059fe..35800927 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,21 @@ "homepage": "https://elk.zone/", "scripts": { "build": "nuxi build", + "build:pwa": "VITE_DEV_PWA=true nuxi build", + "build:netlify:pwa": "VITE_DEV_PWA=true NITRO_PRESET=netlify nuxi build", "dev": "nuxi dev --port 5314", + "dev:pwa": "VITE_DEV_PWA=true nuxi dev --port 5314", "dev:mocked": "nuxi dev --port 5314 --dotenv .env.mock", + "dev:mocked:pwa": "VITE_DEV_PWA=true nuxi dev --port 5314 --dotenv .env.mock", + "dev:mocked:pwa:ssl": "VITE_DEV_PWA=true nuxi dev --port 5314 --https --ssl-cert ./https-dev-config/localhost.crt --ssl-key ./https-dev-config/localhost.key --dotenv .env.mock", "start": "PORT=5314 node .output/server/index.mjs", + "start:https": "PORT=5314 node ./https-dev-config/local-https-server.mjs", "lint": "eslint .", "typecheck": "nuxi typecheck", "prepare": "esno scripts/prepare.ts", "generate": "nuxi generate", "test:unit": "vitest", - "test:typecheck": "vue-tsc --noEmit", + "test:typecheck": "vue-tsc --noEmit && vue-tsc --noEmit --project service-worker/tsconfig.json", "test": "nr test:unit", "postinstall": "nuxi prepare && simple-git-hooks" }, @@ -82,9 +88,11 @@ "ultrahtml": "^1.0.4", "unplugin-auto-import": "^0.12.0", "vite-plugin-inspect": "^0.7.9", + "vite-plugin-pwa": "^0.13.3", "vitest": "^0.25.3", "vue-tsc": "^1.0.11", - "vue-virtual-scroller": "2.0.0-beta.4" + "vue-virtual-scroller": "2.0.0-beta.4", + "workbox-window": "^6.5.4" }, "simple-git-hooks": { "pre-commit": "pnpm lint-staged" diff --git a/pages/notifications.vue b/pages/notifications.vue index 591a882c..2f059c5d 100644 --- a/pages/notifications.vue +++ b/pages/notifications.vue @@ -4,6 +4,8 @@ definePageMeta({ }) const { t } = useI18n() +const showSettings = ref(false) +const pwaEnabled = useRuntimeConfig().public.pwaEnabled const tabs = $computed(() => [ { @@ -17,6 +19,10 @@ const tabs = $computed(() => [ display: t('tab.notifications_mention'), }, ] as const) + +onActivated(() => { + showSettings.value = false +}) + + - + + + + + diff --git a/plugins/masto.ts b/plugins/masto.ts index aac2d301..3d33b925 100644 --- a/plugins/masto.ts +++ b/plugins/masto.ts @@ -43,7 +43,11 @@ export default defineNuxtPlugin(async (nuxtApp) => { if (process.client) { const { query } = useRoute() const user = typeof query.server === 'string' && typeof query.token === 'string' - ? { server: query.server, token: query.token } + ? { + server: query.server, + token: query.token, + vapidKey: typeof query.vapid_key === 'string' ? query.vapid_key : undefined, + } : currentUser.value nuxtApp.hook('app:suspense:resolve', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2d1a95e..71dafc17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,9 +62,11 @@ specifiers: ultrahtml: ^1.0.4 unplugin-auto-import: ^0.12.0 vite-plugin-inspect: ^0.7.9 + vite-plugin-pwa: ^0.13.3 vitest: ^0.25.3 vue-tsc: ^1.0.11 vue-virtual-scroller: 2.0.0-beta.4 + workbox-window: ^6.5.4 dependencies: '@fnando/sparkline': 0.3.10 @@ -130,9 +132,11 @@ devDependencies: ultrahtml: 1.0.4 unplugin-auto-import: 0.12.0 vite-plugin-inspect: 0.7.9 + vite-plugin-pwa: 0.13.3 vitest: 0.25.3_jsdom@20.0.3 vue-tsc: 1.0.11_typescript@4.9.3 vue-virtual-scroller: 2.0.0-beta.4 + workbox-window: 6.5.4 packages: @@ -257,6 +261,18 @@ packages: resolution: {integrity: sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==} dev: true + /@apideck/better-ajv-errors/0.3.6_ajv@8.11.2: + resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} + engines: {node: '>=10'} + peerDependencies: + ajv: '>=8' + dependencies: + ajv: 8.11.2 + json-schema: 0.4.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + dev: true + /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -308,6 +324,14 @@ packages: '@babel/types': 7.20.5 dev: true + /@babel/helper-builder-binary-assignment-operator-visitor/7.18.9: + resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-explode-assignable-expression': 7.18.6 + '@babel/types': 7.20.5 + dev: true + /@babel/helper-compilation-targets/7.20.0_@babel+core@7.20.5: resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} engines: {node: '>=6.9.0'} @@ -339,11 +363,45 @@ packages: - supports-color dev: true + /@babel/helper-create-regexp-features-plugin/7.20.5_@babel+core@7.20.5: + resolution: {integrity: sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + regexpu-core: 5.2.2 + dev: true + + /@babel/helper-define-polyfill-provider/0.3.3_@babel+core@7.20.5: + resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-environment-visitor/7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} dev: true + /@babel/helper-explode-assignable-expression/7.18.6: + resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.5 + dev: true + /@babel/helper-function-name/7.19.0: resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} engines: {node: '>=6.9.0'} @@ -401,6 +459,21 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-remap-async-to-generator/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-wrap-function': 7.20.5 + '@babel/types': 7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-replace-supers/7.19.1: resolution: {integrity: sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==} engines: {node: '>=6.9.0'} @@ -421,6 +494,13 @@ packages: '@babel/types': 7.20.5 dev: true + /@babel/helper-skip-transparent-expression-wrappers/7.20.0: + resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.5 + dev: true + /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} @@ -443,6 +523,18 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-wrap-function/7.20.5: + resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.19.0 + '@babel/template': 7.18.10 + '@babel/traverse': 7.20.5 + '@babel/types': 7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helpers/7.20.6: resolution: {integrity: sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==} engines: {node: '>=6.9.0'} @@ -471,6 +563,277 @@ packages: '@babel/types': 7.20.5 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-async-generator-functions/7.20.1_@babel+core@7.20.5: + resolution: {integrity: sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-class-static-block/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-export-namespace-from/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-logical-assignment-operators/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-object-rest-spread/7.20.2_@babel+core@7.20.5: + resolution: {integrity: sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.5 + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-transform-parameters': 7.20.5_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-optional-chaining/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.5 + dev: true + + /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-private-property-in-object/7.20.5_@babel+core@7.20.5: + resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.5: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.20.5: + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.20.5: + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-import-assertions/7.20.0_@babel+core@7.20.5: + resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.20.5: resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} engines: {node: '>=6.9.0'} @@ -481,6 +844,80 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.20.5: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.20.5: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.20.5: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.20.5: + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.20.5: + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.20.5: resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} engines: {node: '>=6.9.0'} @@ -491,6 +928,345 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-arrow-functions/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-async-to-generator/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-block-scoping/7.20.5_@babel+core@7.20.5: + resolution: {integrity: sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-classes/7.20.2_@babel+core@7.20.5: + resolution: {integrity: sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.19.1 + '@babel/helper-split-export-declaration': 7.18.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-computed-properties/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-destructuring/7.20.2_@babel+core@7.20.5: + resolution: {integrity: sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-duplicate-keys/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.20.5: + resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-function-name/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-literals/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-modules-amd/7.19.6_@babel+core@7.20.5: + resolution: {integrity: sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-commonjs/7.19.6_@babel+core@7.20.5: + resolution: {integrity: sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-simple-access': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-systemjs/7.19.6_@babel+core@7.20.5: + resolution: {integrity: sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-identifier': 7.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-transforms': 7.20.2 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex/7.20.5_@babel+core@7.20.5: + resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-parameters/7.20.5_@babel+core@7.20.5: + resolution: {integrity: sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-regenerator/7.20.5_@babel+core@7.20.5: + resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + regenerator-transform: 0.15.1 + dev: true + + /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-spread/7.19.0_@babel+core@7.20.5: + resolution: {integrity: sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + dev: true + + /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-template-literals/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-typeof-symbol/7.18.9_@babel+core@7.20.5: + resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-typescript/7.20.2_@babel+core@7.20.5: resolution: {integrity: sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag==} engines: {node: '>=6.9.0'} @@ -505,6 +1281,133 @@ packages: - supports-color dev: true + /@babel/plugin-transform-unicode-escapes/7.18.10_@babel+core@7.20.5: + resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.20.5: + resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/preset-env/7.20.2_@babel+core@7.20.5: + resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.5 + '@babel/core': 7.20.5 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-proposal-async-generator-functions': 7.20.1_@babel+core@7.20.5 + '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-class-static-block': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-export-namespace-from': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-logical-assignment-operators': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-object-rest-spread': 7.20.2_@babel+core@7.20.5 + '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-proposal-private-property-in-object': 7.20.5_@babel+core@7.20.5 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.5 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.20.5 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-import-assertions': 7.20.0_@babel+core@7.20.5 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.5 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.20.5 + '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-async-to-generator': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-block-scoping': 7.20.5_@babel+core@7.20.5 + '@babel/plugin-transform-classes': 7.20.2_@babel+core@7.20.5 + '@babel/plugin-transform-computed-properties': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-transform-destructuring': 7.20.2_@babel+core@7.20.5 + '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-duplicate-keys': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.20.5 + '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-modules-amd': 7.19.6_@babel+core@7.20.5 + '@babel/plugin-transform-modules-commonjs': 7.19.6_@babel+core@7.20.5 + '@babel/plugin-transform-modules-systemjs': 7.19.6_@babel+core@7.20.5 + '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5_@babel+core@7.20.5 + '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-parameters': 7.20.5_@babel+core@7.20.5 + '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-regenerator': 7.20.5_@babel+core@7.20.5 + '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-spread': 7.19.0_@babel+core@7.20.5 + '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-transform-typeof-symbol': 7.18.9_@babel+core@7.20.5 + '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.20.5 + '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.20.5 + '@babel/preset-modules': 0.1.5_@babel+core@7.20.5 + '@babel/types': 7.20.5 + babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.20.5 + babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.20.5 + babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.20.5 + core-js-compat: 3.26.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-modules/0.1.5_@babel+core@7.20.5: + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.5 + '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.5 + '@babel/types': 7.20.5 + esutils: 2.0.3 + dev: true + + /@babel/runtime/7.20.6: + resolution: {integrity: sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: true + /@babel/standalone/7.20.6: resolution: {integrity: sha512-u5at/CbBLETf7kx2LOY4XdhseD79Y099WZKAOMXeT8qvd9OSR515my2UNBBLY4qIht/Qi9KySeQHQwQwxJN4Sw==} engines: {node: '>=6.9.0'} @@ -1183,6 +2086,23 @@ packages: slash: 4.0.0 dev: true + /@rollup/plugin-babel/5.3.1_opjstonlpkhafnz76jsxdwq25a: + resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} + engines: {node: '>= 10.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-module-imports': 7.18.6 + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + rollup: 2.79.1 + dev: true + /@rollup/plugin-commonjs/23.0.3_rollup@2.79.1: resolution: {integrity: sha512-31HxrT5emGfTyIfAs1lDQHj6EfYxTXcwtX5pIIhq+B/xZBNIqQ179d/CkYxlpYmFCxT78AeU4M8aL8Iv/IBxFA==} engines: {node: '>=14.0.0'} @@ -1229,6 +2149,21 @@ packages: rollup: 2.79.1 dev: true + /@rollup/plugin-node-resolve/11.2.1_rollup@2.79.1: + resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} + engines: {node: '>= 10.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + '@types/resolve': 1.17.1 + builtin-modules: 3.3.0 + deepmerge: 4.2.2 + is-module: 1.0.0 + resolve: 1.22.1 + rollup: 2.79.1 + dev: true + /@rollup/plugin-node-resolve/15.0.1_rollup@2.79.1: resolution: {integrity: sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==} engines: {node: '>=14.0.0'} @@ -1247,6 +2182,26 @@ packages: rollup: 2.79.1 dev: true + /@rollup/plugin-replace/2.4.2_rollup@2.79.1: + resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + magic-string: 0.25.9 + rollup: 2.79.1 + dev: true + + /@rollup/plugin-replace/4.0.0_rollup@2.79.1: + resolution: {integrity: sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + magic-string: 0.25.9 + rollup: 2.79.1 + dev: true + /@rollup/plugin-replace/5.0.1_rollup@2.79.1: resolution: {integrity: sha512-Z3MfsJ4CK17BfGrZgvrcp/l6WXoKb0kokULO+zt/7bmcyayokDaQ2K3eDJcRLCTAlp5FPI4/gz9MHAsosz4Rag==} engines: {node: '>=14.0.0'} @@ -1273,6 +2228,18 @@ packages: rollup: 2.79.1 dev: true + /@rollup/pluginutils/3.1.0_rollup@2.79.1: + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: true + /@rollup/pluginutils/4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -1310,6 +2277,15 @@ packages: rollup: 2.79.1 dev: true + /@surma/rollup-plugin-off-main-thread/2.2.3: + resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + dependencies: + ejs: 3.1.8 + json5: 2.2.1 + magic-string: 0.25.9 + string.prototype.matchall: 4.0.8 + dev: true + /@tauri-apps/api/1.2.0: resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} @@ -1612,6 +2588,10 @@ packages: resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} dev: true + /@types/estree/0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + dev: true + /@types/estree/1.0.0: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true @@ -1656,6 +2636,12 @@ packages: resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} dev: true + /@types/resolve/1.17.1: + resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} + dependencies: + '@types/node': 18.11.10 + dev: true + /@types/resolve/1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true @@ -1664,6 +2650,10 @@ packages: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} dev: true + /@types/trusted-types/2.0.2: + resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} + dev: true + /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true @@ -2608,6 +3598,15 @@ packages: uri-js: 4.4.1 dev: true + /ajv/8.11.2: + resolution: {integrity: sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: true + /ansi-escapes/4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -2763,6 +3762,11 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true + /at-least-node/1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + dev: true + /autoprefixer/10.4.13_postcss@8.4.19: resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} @@ -2789,6 +3793,42 @@ packages: - debug dev: true + /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.20.5: + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.5 + '@babel/core': 7.20.5 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.5 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-corejs3/0.6.0_@babel+core@7.20.5: + resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.5 + core-js-compat: 3.26.1 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-regenerator/0.4.1_@babel+core@7.20.5: + resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.5 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.5 + transitivePeerDependencies: + - supports-color + dev: true + /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -3211,6 +4251,11 @@ packages: engines: {node: ^12.20.0 || >=14} dev: true + /common-tags/1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + dev: true + /commondir/1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: true @@ -3253,6 +4298,12 @@ packages: resolution: {integrity: sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==} dev: true + /core-js-compat/3.26.1: + resolution: {integrity: sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==} + dependencies: + browserslist: 4.21.4 + dev: true + /core-util-is/1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true @@ -3284,6 +4335,11 @@ packages: which: 2.0.2 dev: true + /crypto-random-string/2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + dev: true + /css-declaration-sorter/6.3.1_postcss@8.4.19: resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} engines: {node: ^10 || ^12 || >=14} @@ -3656,6 +4712,14 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true + /ejs/3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.5 + dev: true + /electron-to-chromium/1.4.284: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} dev: true @@ -4385,6 +5449,10 @@ packages: resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} dev: true + /estree-walker/1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + dev: true + /estree-walker/2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -4511,6 +5579,12 @@ packages: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: true + /filelist/1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + dependencies: + minimatch: 5.1.1 + dev: true + /fill-range/7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -4632,6 +5706,16 @@ packages: universalify: 2.0.0 dev: true + /fs-extra/9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs-memo/1.2.0: resolution: {integrity: sha512-YEexkCpL4j03jn5SxaMHqcO6IuWuqm8JFUYhyCep7Ao89JIYmB8xoKhK7zXXJ9cCaNXpyNH5L3QtAmoxjoHW2w==} dev: true @@ -4721,6 +5805,10 @@ packages: has-symbols: 1.0.3 dev: true + /get-own-enumerable-property-symbols/3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + dev: true + /get-port-please/2.6.1: resolution: {integrity: sha512-4PDSrL6+cuMM1xs6w36ZIkaKzzE0xzfVBCfebHIJ3FE8iB9oic/ECwPw3iNiD4h1AoJ5XLLBhEviFAVrZsDC5A==} dependencies: @@ -5032,6 +6120,10 @@ packages: safer-buffer: 2.1.2 dev: true + /idb/7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + dev: true + /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -5259,6 +6351,11 @@ packages: engines: {node: '>=0.12.0'} dev: true + /is-obj/1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + dev: true + /is-path-inside/3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -5291,6 +6388,11 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-regexp/1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + dev: true + /is-shared-array-buffer/1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -5367,6 +6469,17 @@ packages: ws: 8.11.0 dev: true + /jake/10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + dev: true + /jest-worker/26.6.2: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} @@ -5467,6 +6580,14 @@ packages: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true + + /json-schema/0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: true + /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true @@ -5517,6 +6638,11 @@ packages: graceful-fs: 4.2.10 dev: true + /jsonpointer/5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + dev: true + /klona/2.0.5: resolution: {integrity: sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==} engines: {node: '>= 8'} @@ -5537,6 +6663,11 @@ packages: readable-stream: 2.3.7 dev: true + /leven/3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + /levn/0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} engines: {node: '>= 0.8.0'} @@ -5676,6 +6807,10 @@ packages: resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} dev: true + /lodash.sortby/4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true + /lodash.template/4.5.0: resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} dependencies: @@ -6993,6 +8128,11 @@ packages: hasBin: true dev: true + /pretty-bytes/5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + dev: true + /pretty-bytes/6.0.0: resolution: {integrity: sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==} engines: {node: ^14.13.1 || >=16.0.0} @@ -7201,6 +8341,27 @@ packages: redis-errors: 1.2.0 dev: true + /regenerate-unicode-properties/10.1.0: + resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: true + + /regenerate/1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: true + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: true + + /regenerator-transform/0.15.1: + resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} + dependencies: + '@babel/runtime': 7.20.6 + dev: true + /regexp-tree/0.1.24: resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==} hasBin: true @@ -7220,6 +8381,22 @@ packages: engines: {node: '>=8'} dev: true + /regexpu-core/5.2.2: + resolution: {integrity: sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.0 + regjsgen: 0.7.1 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: true + + /regjsgen/0.7.1: + resolution: {integrity: sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==} + dev: true + /regjsparser/0.9.1: resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} hasBin: true @@ -7232,6 +8409,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true + /requires-port/1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true @@ -7598,6 +8780,13 @@ packages: engines: {node: '>= 8'} dev: true + /source-map/0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: true + /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead @@ -7671,6 +8860,19 @@ packages: strip-ansi: 7.0.1 dev: true + /string.prototype.matchall/4.0.8: + resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.4 + get-intrinsic: 1.1.3 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + dev: true + /string.prototype.trimend/1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: @@ -7699,6 +8901,15 @@ packages: safe-buffer: 5.2.1 dev: true + /stringify-object/3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + dev: true + /strip-ansi/6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -7718,6 +8929,11 @@ packages: engines: {node: '>=4'} dev: true + /strip-comments/2.0.1: + resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} + engines: {node: '>=10'} + dev: true + /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -7854,6 +9070,21 @@ packages: yallist: 4.0.0 dev: true + /temp-dir/2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + dev: true + + /tempy/0.6.0: + resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} + engines: {node: '>=10'} + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + dev: true + /terser/5.16.1: resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==} engines: {node: '>=10'} @@ -7945,6 +9176,12 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: true + /tr46/1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.1.1 + dev: true + /tr46/3.0.0: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} @@ -8008,6 +9245,11 @@ packages: engines: {node: '>=4'} dev: true + /type-fest/0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -8106,6 +9348,29 @@ packages: hookable: 5.4.2 dev: true + /unicode-canonical-property-names-ecmascript/2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: true + + /unicode-match-property-ecmascript/2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: true + + /unicode-match-property-value-ecmascript/2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: true + + /unicode-property-aliases-ecmascript/2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: true + /unimport/1.0.1: resolution: {integrity: sha512-SEPKl3uyqUvi6c0MnyCmUF9H07CuC9j9p2p33F03LmegU0sxjpnjL0fLKAhh7BTfcKaJKj+1iOiAFtg7P3m5mQ==} dependencies: @@ -8142,6 +9407,13 @@ packages: - rollup dev: true + /unique-string/2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + dependencies: + crypto-random-string: 2.0.0 + dev: true + /unist-util-stringify-position/2.0.3: resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} dependencies: @@ -8320,6 +9592,11 @@ packages: - supports-color dev: true + /upath/1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + dev: true + /update-browserslist-db/1.0.10_browserslist@4.21.4: resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} hasBin: true @@ -8444,6 +9721,23 @@ packages: - supports-color dev: true + /vite-plugin-pwa/0.13.3: + resolution: {integrity: sha512-cjWXpZ7slAY14OKz7M8XdgTIi9wjf6OD6NkhiMAc+ogxnbUrecUwLdRtfGPCPsN2ftut5gaN1jTghb11p6IQAA==} + peerDependencies: + vite: ^3.1.0 + dependencies: + '@rollup/plugin-replace': 4.0.0_rollup@2.79.1 + debug: 4.3.4 + fast-glob: 3.2.12 + pretty-bytes: 6.0.0 + rollup: 2.79.1 + workbox-build: 6.5.4 + workbox-window: 6.5.4 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + dev: true + /vite/3.2.4: resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8772,6 +10066,10 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true + /webidl-conversions/4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true + /webidl-conversions/7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -8813,6 +10111,14 @@ packages: webidl-conversions: 3.0.1 dev: true + /whatwg-url/7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: true + /which-boxed-primitive/1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -8842,6 +10148,152 @@ packages: engines: {node: '>=0.10.0'} dev: true + /workbox-background-sync/6.5.4: + resolution: {integrity: sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==} + dependencies: + idb: 7.1.1 + workbox-core: 6.5.4 + dev: true + + /workbox-broadcast-update/6.5.4: + resolution: {integrity: sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-build/6.5.4: + resolution: {integrity: sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==} + engines: {node: '>=10.0.0'} + dependencies: + '@apideck/better-ajv-errors': 0.3.6_ajv@8.11.2 + '@babel/core': 7.20.5 + '@babel/preset-env': 7.20.2_@babel+core@7.20.5 + '@babel/runtime': 7.20.6 + '@rollup/plugin-babel': 5.3.1_opjstonlpkhafnz76jsxdwq25a + '@rollup/plugin-node-resolve': 11.2.1_rollup@2.79.1 + '@rollup/plugin-replace': 2.4.2_rollup@2.79.1 + '@surma/rollup-plugin-off-main-thread': 2.2.3 + ajv: 8.11.2 + common-tags: 1.8.2 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + lodash: 4.17.21 + pretty-bytes: 5.6.0 + rollup: 2.79.1 + rollup-plugin-terser: 7.0.2_rollup@2.79.1 + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 6.5.4 + workbox-broadcast-update: 6.5.4 + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-google-analytics: 6.5.4 + workbox-navigation-preload: 6.5.4 + workbox-precaching: 6.5.4 + workbox-range-requests: 6.5.4 + workbox-recipes: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + workbox-streams: 6.5.4 + workbox-sw: 6.5.4 + workbox-window: 6.5.4 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + dev: true + + /workbox-cacheable-response/6.5.4: + resolution: {integrity: sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-core/6.5.4: + resolution: {integrity: sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==} + dev: true + + /workbox-expiration/6.5.4: + resolution: {integrity: sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==} + dependencies: + idb: 7.1.1 + workbox-core: 6.5.4 + dev: true + + /workbox-google-analytics/6.5.4: + resolution: {integrity: sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==} + dependencies: + workbox-background-sync: 6.5.4 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + dev: true + + /workbox-navigation-preload/6.5.4: + resolution: {integrity: sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-precaching/6.5.4: + resolution: {integrity: sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==} + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + dev: true + + /workbox-range-requests/6.5.4: + resolution: {integrity: sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-recipes/6.5.4: + resolution: {integrity: sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==} + dependencies: + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-precaching: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + dev: true + + /workbox-routing/6.5.4: + resolution: {integrity: sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-strategies/6.5.4: + resolution: {integrity: sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-streams/6.5.4: + resolution: {integrity: sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==} + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + dev: true + + /workbox-sw/6.5.4: + resolution: {integrity: sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==} + dev: true + + /workbox-window/6.5.4: + resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==} + dependencies: + '@types/trusted-types': 2.0.2 + workbox-core: 6.5.4 + dev: true + /wrap-ansi/6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} diff --git a/public-dev/apple-touch-icon.png b/public-dev/apple-touch-icon.png new file mode 100644 index 00000000..f134f6da Binary files /dev/null and b/public-dev/apple-touch-icon.png differ diff --git a/public-dev/pwa-192x192.png b/public-dev/pwa-192x192.png new file mode 100644 index 00000000..a1aeffba Binary files /dev/null and b/public-dev/pwa-192x192.png differ diff --git a/public-dev/pwa-512x512.png b/public-dev/pwa-512x512.png new file mode 100644 index 00000000..53dd3688 Binary files /dev/null and b/public-dev/pwa-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 00000000..8ab60366 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 00000000..78d04f7c --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/pwa-192x192.png b/public/pwa-192x192.png new file mode 100644 index 00000000..660e94e3 Binary files /dev/null and b/public/pwa-192x192.png differ diff --git a/public/pwa-512x512.png b/public/pwa-512x512.png new file mode 100644 index 00000000..48c1e184 Binary files /dev/null and b/public/pwa-512x512.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..c2a49f4f --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 00000000..15b6f9b7 --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,79 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + diff --git a/server/api/[server]/oauth.ts b/server/api/[server]/oauth.ts index 8c8e33a0..84f6a4aa 100644 --- a/server/api/[server]/oauth.ts +++ b/server/api/[server]/oauth.ts @@ -32,6 +32,6 @@ export default defineEventHandler(async (event) => { }, }) - const url = `/signin/callback?${stringifyQuery({ server, token: result.access_token })}` + const url = `/signin/callback?${stringifyQuery({ server, token: result.access_token, vapid_key: app.vapid_key })}` await sendRedirect(event, url, 302) }) diff --git a/service-worker/sw.ts b/service-worker/sw.ts new file mode 100644 index 00000000..6294655c --- /dev/null +++ b/service-worker/sw.ts @@ -0,0 +1,65 @@ +/// +/// +import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching' +import { NavigationRoute, registerRoute } from 'workbox-routing' +import { onNotificationClick, onPush } from './web-push-notifications' + +declare const self: ServiceWorkerGlobalScope +/* +import { CacheableResponsePlugin } from 'workbox-cacheable-response' +import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies' +import { ExpirationPlugin } from 'workbox-expiration' +*/ + +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') + self.skipWaiting() +}) + +const entries = self.__WB_MANIFEST +if (import.meta.env.DEV) + entries.push({ url: '/', revision: Math.random().toString() }) + +precacheAndRoute(entries) + +// clean old assets +cleanupOutdatedCaches() + +// allow only fallback in dev: we don't want to cache anything +let allowlist: undefined | RegExp[] +if (import.meta.env.DEV) + allowlist = [/^\/$/] + +// deny api and server page calls +let denylist: undefined | RegExp[] +if (import.meta.env.PROD) + denylist = [/^\/api\//, /^\/login\//, /^\/oauth\//, /^\/signin\//] + +// only cache pages and external assets on local build + start or in production +if (import.meta.env.PROD) { + // external assets: rn avatars from mas.to + // requires and http header: Allow-Control-Allow-Origin: * +/* + registerRoute( + ({ sameOrigin, request }) => !sameOrigin && request.destination === 'image', + new NetworkFirst({ + cacheName: 'elk-external-media', + plugins: [ + // add opaque responses? + new CacheableResponsePlugin({ statuses: [/!* 0, *!/200] }), + // 15 days max + new ExpirationPlugin({ maxAgeSeconds: 60 * 60 * 24 * 15 }), + ], + }), + ) +*/ +} + +// to allow work offline +registerRoute(new NavigationRoute( + createHandlerBoundToURL('/'), + { allowlist, denylist }, +)) + +self.addEventListener('push', onPush) +self.addEventListener('notificationclick', onNotificationClick) diff --git a/service-worker/tsconfig.json b/service-worker/tsconfig.json new file mode 100644 index 00000000..78e5707a --- /dev/null +++ b/service-worker/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "WebWorker"], + "types": ["vite/client", "service-worker"] + }, + "include": ["./"], + "extends": "../tsconfig.json" +} diff --git a/service-worker/types.ts b/service-worker/types.ts new file mode 100644 index 00000000..7c6aec3a --- /dev/null +++ b/service-worker/types.ts @@ -0,0 +1,9 @@ +export interface PushPayload { + access_token: string + notification_id: string + notification_type: 'follow' | 'favourite' | 'reblog' | 'mention' | 'poll' + preferred_locale: string + title: string + body: string + icon: string +} diff --git a/service-worker/web-push-notifications.ts b/service-worker/web-push-notifications.ts new file mode 100644 index 00000000..18e66d9a --- /dev/null +++ b/service-worker/web-push-notifications.ts @@ -0,0 +1,85 @@ +/// +/// +import type { PushPayload } from '~/service-worker/types' + +declare const self: ServiceWorkerGlobalScope + +export const onPush = (event: PushEvent) => { + const promise = isClientFocused().then((isFocused) => { + if (isFocused) + return Promise.resolve() + + const options: PushPayload = event.data!.json() + const { + access_token, + body, + icon, + notification_id, + notification_type, + preferred_locale, + } = options + + let url = 'home' + if (notification_type) { + switch (notification_type) { + case 'follow': + url = 'notifications' + break + case 'mention': + url = 'notifications/mention' + break + } + } + + const notificationOptions: NotificationOptions = { + badge: '/pwa-192x192.png', + body, + data: { + access_token, + preferred_locale, + url: `/${url}`, + }, + dir: 'auto', + icon, + lang: preferred_locale, + tag: notification_id, + timestamp: new Date().getUTCDate(), + } + return self.registration.showNotification(options.title, notificationOptions) + }) + + event.waitUntil(promise) +} + +export const onNotificationClick = (event: NotificationEvent) => { + const reactToNotificationClick = new Promise((resolve) => { + event.notification.close() + resolve(openUrl(event.notification.data.url)) + }) + + event.waitUntil(reactToNotificationClick) +} + +function findBestClient(clients: WindowClient[]) { + const focusedClient = clients.find(client => client.focused) + const visibleClient = clients.find(client => client.visibilityState === 'visible') + + return focusedClient || visibleClient || clients[0] +} + +async function openUrl(url: string) { + const clients = await self.clients.matchAll({ type: 'window' }) + // Chrome 42-48 does not support navigate + if (clients.length !== 0 && 'navigate' in clients[0]) { + const client = findBestClient(clients as WindowClient[]) + await client.navigate(url).then(client => client?.focus()) + } + + await self.clients.openWindow(url) +} + +function isClientFocused() { + return self.clients + .matchAll({ type: 'window', includeUncontrolled: true }) + .then(windowClients => Promise.resolve(windowClients.some(windowClient => windowClient.focused))) +} diff --git a/shims.d.ts b/shims.d.ts index d791bcd2..e82d75f0 100644 --- a/shims.d.ts +++ b/shims.d.ts @@ -1 +1,3 @@ /// +/// +/// diff --git a/types/index.ts b/types/index.ts index c8b2df51..c1b5f852 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,4 +1,4 @@ -import type { Account, AccountCredentials, Attachment, CreateStatusParams, Emoji, Instance, MastoClient, Notification, Status } from 'masto' +import type { Account, AccountCredentials, Attachment, CreateStatusParams, Emoji, Instance, MastoClient, Notification, PushSubscription, Status } from 'masto' import type { Ref } from 'vue' import type { Mutable } from './utils' @@ -16,6 +16,8 @@ export interface UserLogin { server: string token?: string account: AccountCredentials + vapidKey?: string + pushSubscription?: PushSubscription } export interface ElkMasto extends MastoClient {