Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
14 KB
Referenced Files
None
Subscribers
None
diff --git a/src/main.js b/src/main.js
index 4b9799fe30..a520b54b85 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,91 +1,92 @@
/* global process */
import { createPinia } from 'pinia'
import './lib/event_target_polyfill.js'
// Polyfill for Array.prototype.toSorted (ES2023)
if (!Array.prototype.toSorted) {
Array.prototype.toSorted = function (compareFn) {
return [...this].sort(compareFn)
}
}
import { createI18n } from 'vue-i18n'
import afterStoreSetup from './boot/after_store.js'
import messages from './i18n/messages.js'
import { piniaPersistPlugin } from './lib/persisted_state.js'
import { piniaPushNotificationsPlugin } from './lib/push_notifications_plugin.js'
import { piniaLanguagePlugin } from 'src/lib/language.js'
import { piniaStylePlugin } from 'src/lib/style.js'
const currentLocale = (window.navigator.language || 'en').split('-')[0]
const i18n = createI18n({
// By default, use the browser locale, we will update it if neccessary
locale: 'en',
fallbackLocale: 'en',
messages: messages.default,
})
messages.setLanguage(i18n.global, currentLocale)
;(async () => {
const isFox = Math.floor(Math.random() * 2) > 0 ? '_fox' : ''
const splashError = (i18n, e) => {
const throbber = document.querySelector('#throbber')
throbber.addEventListener('animationend', () => {
document.querySelector('#mascot').src =
`/static/pleromatan_orz${isFox}.png`
})
throbber.classList.add('dead')
document.querySelector('#status').textContent =
i18n.global.t('splash.error')
console.error('PleromaFE failed to initialize: ', e)
document.querySelector('#statusError').textContent = e
document.querySelector('#statusStack').textContent = e.stack
document.querySelector('#statusError').style = 'display: block'
document.querySelector('#statusStack').style = 'display: block'
}
window.splashError = (e) => splashError(i18n, e)
window.splashUpdate = (key) => {
if (document.querySelector('#status')) {
document.querySelector('#status').textContent = i18n.global.t(key)
}
}
try {
let storageError
const pinia = createPinia()
pinia.use(piniaPersistPlugin())
pinia.use(piniaLanguagePlugin)
pinia.use(piniaStylePlugin)
pinia.use(piniaPushNotificationsPlugin)
document.querySelector('#splash').classList.remove('initial-hidden')
document.querySelector('#mascot').src =
`/static/pleromatan_apology${isFox}_small.webp`
document.querySelector('#status').removeAttribute('class')
document.querySelector('#status').textContent =
i18n.global.t('splash.loading')
document.querySelector('#splash-credit').textContent = i18n.global.t(
'update.art_by',
{ linkToArtist: 'pipivovott' },
)
// Temporarily passing pinia stores along with storageError result until migration is fully complete.
return await afterStoreSetup({ pinia, storageError, i18n })
} catch (e) {
splashError(i18n, e)
}
})()
// These are inlined by webpack's DefinePlugin
// biome-ignore-start lint: added in build process
window.___pleromafe_mode = process.env
window.___pleromafe_commit_hash = COMMIT_HASH
window.___pleromafe_dev_overrides = DEV_OVERRIDES
+window.___pleromafe_hosted_mode = HOSTED_MODE
// biome-ignore-end lint: added in build process
diff --git a/src/services/hosted/hosted.js b/src/services/hosted/hosted.js
index 323d72821a..3a4e744b39 100644
--- a/src/services/hosted/hosted.js
+++ b/src/services/hosted/hosted.js
@@ -1,57 +1,58 @@
// Hosted mode: this frontend served from its own domain, logging into an
// instance the user picks. The site turns it on in its static/config.json
// ("hosted": true); an instance serving the frontend itself never does.
const INSTANCE_KEY = 'pleroma-fe-hosted-instance'
-export const isHosted = (staticConfig) => staticConfig?.hosted === true
+export const isHosted = (staticConfig) =>
+ staticConfig?.hosted === true || window.___pleromafe_hosted_mode
// "pleroma.example", "@me@pleroma.example" or a URL on it -> its origin
export const instanceOrigin = (input) => {
const text = (input ?? '').trim().replace(/^@?[^@\s/]+@/, '')
if (!text) return null
try {
const url = new URL(/^https?:\/\//i.test(text) ? text : `https://${text}`)
const local = url.hostname === 'localhost'
if (!url.hostname.includes('.') && !local) return null
// the login token never goes over plain http to a real server
if (!local) url.protocol = 'https:'
return url.origin
} catch {
return null
}
}
export const chosenInstance = () => localStorage.getItem(INSTANCE_KEY)
export const chooseInstance = (origin) =>
localStorage.setItem(INSTANCE_KEY, origin)
export const forgetInstance = () => localStorage.removeItem(INSTANCE_KEY)
// an instance answers /api/v1/instance with JSON, and lets this site ask
export const checkInstance = async (origin, fetch = window.fetch) => {
try {
const res = await fetch(`${origin}/api/v1/instance`)
if (!res.ok) return false
const data = await res.json()
return typeof data?.uri === 'string'
} catch {
return false
}
}
// The instance's frontend configuration, for use on this site: its paths
// (logo, background, default avatar…) point at the instance, app routes
// stay routes, and login goes through the instance's own login page.
export const instanceConfig = (config, resolve) => ({
...Object.fromEntries(
Object.entries(config ?? {}).map(([key, value]) => [
key,
typeof value === 'string' &&
value.startsWith('/') &&
!key.startsWith('redirect')
? resolve(value)
: value,
]),
),
loginMethod: 'token',
})
diff --git a/tools/spa.nginx.conf b/tools/spa.nginx.conf
new file mode 100644
index 0000000000..71778edd90
--- /dev/null
+++ b/tools/spa.nginx.conf
@@ -0,0 +1,23 @@
+location ^~ /api {
+ return 404;
+}
+
+location ^~ /auth {
+ return 404;
+}
+
+location ^~ /nodeinfo {
+ return 404;
+}
+
+location ^~ /instance {
+ return 404;
+}
+
+location ^~ /oauth {
+ return 404;
+}
+
+location / {
+ try_files $uri $uri/ /index.html;
+}
diff --git a/vite.config.js b/vite.config.js
index a67dc9df84..1336edea35 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,258 +1,259 @@
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vite'
import biomePlugin from 'vite-plugin-biome'
import stylelint from 'vite-plugin-stylelint'
import vueDevTools from 'vite-plugin-vue-devtools'
import { configDefaults } from 'vitest/config'
import { getCommitHash } from './build/commit_hash.js'
import copyPlugin from './build/copy_plugin.js'
import emojisPlugin from './build/emojis_plugin.js'
import { buildSwPlugin, swMessagesPlugin } from './build/sw_plugin.js'
const localConfigPath = '<projectRoot>/config/local.json'
const normalizeTarget = (target) => {
if (!target || typeof target !== 'string') return target
return target.endsWith('/') ? target.replace(/\/$/, '') : target
}
const getLocalDevSettings = async () => {
const envTarget = normalizeTarget(process.env.VITE_PROXY_TARGET)
const envOrigin = normalizeTarget(process.env.VITE_PROXY_ORIGIN)
try {
const settings = (await import('./config/local.json')).default
settings.target = normalizeTarget(settings.target)
settings.origin = normalizeTarget(settings.origin)
if (envTarget) settings.target = envTarget
if (envOrigin) settings.origin = envOrigin
console.info(`Using local dev server settings (${localConfigPath}):`)
console.info(JSON.stringify(settings, null, 2))
return settings
} catch (e) {
if (!envTarget && !envOrigin) {
console.info(
`Local dev server settings not found (${localConfigPath}), using default`,
e,
)
return {}
}
const settings = { target: envTarget, origin: envOrigin }
console.info(
'Using dev server settings from VITE_PROXY_TARGET/VITE_PROXY_ORIGIN:',
)
console.info(JSON.stringify(settings, null, 2))
return settings
}
}
const projectRoot = dirname(fileURLToPath(import.meta.url))
const getTransformSWSettings = (settings) => {
if ('transformSW' in settings) {
return settings.transformSW
} else {
console.info(
'`transformSW` is not present in your local settings.\n' +
'This option controls whether the service worker should be bundled and transformed into iife (immediately-invoked function expression) during development.\n' +
'If set to false, the service worker will be served as-is, as an ES Module.\n' +
'Some browsers (e.g. Firefox) does not support ESM service workers.\n' +
'To avoid surprises, it is defaulted to true, but this can be slow.\n' +
'If you are using a browser that supports ESM service workers, you can set this option to false.\n' +
`No matter your choice, you can set the transformSW option in ${localConfigPath} in to disable this message.`,
)
return true
}
}
export default defineConfig(async ({ mode, command }) => {
const settings = await getLocalDevSettings()
const target = settings.target || 'http://localhost:4000/'
const origin = settings.origin || target
const targetSW = target.replace(/^http/, 'ws')
const transformSW = getTransformSWSettings(settings)
const proxy = {
'/api': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
ws: true,
rewriteWsOrigin: true,
},
'/auth': {
// Mastodon password reset lives here
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
ws: true,
},
'/nodeinfo': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
},
'/instance': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
},
'/oauth': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
},
'/socket': {
target: targetSW,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
rewriteWsOrigin: true,
ws: true,
headers: {
Origin: origin,
},
},
}
const swSrc = 'src/sw.js'
const swDest = 'sw-pleroma.js'
const alias = {
src: '/src',
test: '/test',
components: '/src/components',
...(mode === 'test' ? { vue: 'vue/dist/vue.esm-bundler.js' } : {}),
}
return {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement(tag) {
if (tag === 'pinch-zoom') {
return true
}
if (tag.startsWith('cropper-')) {
return true
}
return false
},
},
},
}),
vueDevTools(),
vueJsx(),
buildSwPlugin({ swSrc, swDest }),
swMessagesPlugin(),
emojisPlugin(),
copyPlugin({
inUrl: '/static/ruffle',
inFs: resolve(projectRoot, 'node_modules/@ruffle-rs/ruffle'),
}),
biomePlugin({
mode: 'check',
}),
stylelint({
lintInWorker: true,
lintOnStart: true,
cacheLocation: resolve(
projectRoot,
'node_modules/.cache/stylelintcache',
),
}),
],
css: {
devSourcemap: true,
},
resolve: {
alias,
},
define: {
'process.env': JSON.stringify({
NODE_ENV:
mode === 'test'
? 'testing'
: command === 'serve'
? 'development'
: 'production',
HAS_MODULE_SERVICE_WORKER: command === 'serve' && !transformSW,
}),
COMMIT_HASH: JSON.stringify(
command === 'serve' ? 'DEV' : getCommitHash(),
),
+ HOSTED_MODE: process.env.PLEROMAFE_HOSTED_MODE,
DEV_OVERRIDES: JSON.stringify(command === 'serve' ? settings : undefined),
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
},
// devtools: { enabled: true },
build: {
sourcemap: true,
rolldownOptions: {
input: {
main: 'index.html',
},
output: {
entryFileNames(chunkInfo) {
const id = chunkInfo.facadeModuleId
if (id.endsWith(swSrc)) {
return swDest
} else {
return 'static/js/[name].[hash].js'
}
},
chunkFileNames(chunkInfo) {
if (chunkInfo.facadeModuleId) {
if (
chunkInfo.facadeModuleId.includes(
'node_modules/@kazvmoe-infra/unicode-emoji-json/annotations/',
)
) {
return 'static/js/emoji-annotations/[name].[hash].js'
} else if (chunkInfo.facadeModuleId.includes('src/i18n/')) {
return 'static/js/i18n/[name].[hash].js'
}
}
return 'static/js/[name].[hash].js'
},
assetFileNames(assetInfo) {
const name = assetInfo.names?.[0] || ''
if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(name)) {
return 'static/img/[name].[hash][extname]'
} else if (/\.css$/.test(name)) {
return 'static/css/[name].[hash][extname]'
} else {
return 'static/misc/[name].[hash][extname]'
}
},
},
},
},
server: {
...(mode === 'test' ? {} : { proxy }),
port: Number(process.env.PORT) || 8080,
},
preview: {
proxy,
},
test: {
globals: true,
exclude: [...configDefaults.exclude, 'test/e2e-playwright/**'],
coverage: {
provider: 'v8',
all: true,
reporter: ['text-summary', ['lcov', { subdir: './src' }]],
exclude: ['**/*.style.js', 'public/**', '**/*.scss'],
},
browser: {
enabled: true,
headless: false,
provider: playwright(),
// https://github.com/mswjs/msw/issues/2757
instances: [{ browser: 'chromium' }],
},
},
}
})

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 30, 9:49 AM (19 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799981
Default Alt Text
(14 KB)

Event Timeline