sunkkoma/src/modules/config.js
Oneric ab250c2f3a Explicitly set SameSite attribute for cookies
Modern browsers start to tighten down on third-party access to cookies.
E.g. in current Firefox, a warning about the userLanguage cookie was
shown since it did not yet explicitly set the SameSite attribute and the
default is about to change.

The cookie name being referred to as BACKEND_LANGUAGE_COOKIE_NAME
suggests it should be readable by the actual Akkoma backend, which can
live at a different domain than akkoma-fe. Thus explicitly enable
sharing with third-party sites.

No warnings were shown for other cookies, so I assume
this was the only one not yet setting SameSite.
2023-10-19 01:05:59 +02:00

273 lines
9.5 KiB
JavaScript

import Cookies from 'js-cookie'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
import localeService from '../services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
const browserLocale = (window.navigator.language || 'en').split('-')[0]
/* TODO this is a bit messy.
* We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta
* in general.
*/
export const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior',
'conversationDisplay', // tree | linear
'conversationOtherRepliesButton', // below | inside
'mentionLinkDisplay' // short | full_for_remote | full
]
export const defaultState = {
profile: 'default',
profileVersion: 0,
expertLevel: 0, // used to track which settings to show and hide
colors: {},
theme: undefined,
customTheme: undefined,
customThemeSource: undefined,
hideISP: false,
hideInstanceWallpaper: false,
// bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default
hideMutedThreads: undefined, // instance default
hideThreadsWithBlockedUsers: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
collapseMessageWithSubject: true, // instance default
padEmoji: true,
showNavShortcuts: undefined, // instance default
showPanelNavShortcuts: undefined, // instance default
showWiderShortcuts: undefined, // instance default
hideSiteFavicon: undefined, // instance default
hideSiteName: undefined, // instance default
hideAttachments: false,
hideAttachmentsInConv: false,
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
loopVideo: true,
loopVideoSilentOnly: true,
streaming: false,
emojiReactionsOnTimeline: true,
alwaysShowNewPostButton: false,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: undefined,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {
follows: true,
mentions: true,
likes: true,
repeats: true,
moves: true,
emojiReactions: true,
followRequest: true,
polls: true
},
webPushNotifications: false,
webPushHideIfCW: true,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: false,
sidebarRight: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
postContentType: undefined, // instance default
// This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default
modalOnRepeat: undefined, // instance default
modalOnUnfollow: undefined, // instance default
modalOnBlock: undefined, // instance default
modalOnMute: undefined, // instance default
modalOnDelete: undefined, // instance default
modalOnLogout: undefined, // instance default
modalOnApproveFollow: undefined, // instance default
modalOnDenyFollow: undefined, // instance default
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default
mentionLinkFadeDomain: undefined, // instance default
mentionLinkShowYous: undefined, // instance default
mentionLinkBoldenYou: undefined, // instance default
hidePostStats: undefined, // instance default
hideBotIndication: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined, // instance default
sensitiveIfSubject: undefined,
renderMisskeyMarkdown: undefined,
renderMfmOnHover: undefined, // instance default
conversationDisplay: undefined, // instance default
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
maxDepthInThread: undefined, // instance default
translationLanguage: undefined, // instance default,
supportedTranslationLanguages: {}, // instance default
userProfileDefaultTab: 'statuses',
useBlurhash: true,
}
// caching the instance default properties
export const instanceDefaultProperties = Object.entries(defaultState)
.filter(([key, value]) => value === undefined)
.map(([key, value]) => key)
const config = {
state: { ...defaultState },
getters: {
defaultConfig (state, getters, rootState, rootGetters) {
const { instance } = rootState
return {
...defaultState,
...Object.fromEntries(
instanceDefaultProperties.map(key => [key, instance[key]])
)
}
},
mergedConfig (state, getters, rootState, rootGetters) {
const { defaultConfig } = rootGetters
return {
...defaultConfig,
// Do not override with undefined
...Object.fromEntries(Object.entries(state).filter(([k, v]) => v !== undefined))
}
}
},
mutations: {
setOption (state, { name, value }) {
state[name] = value
},
setHighlight (state, { user, color, type }) {
const data = this.state.config.highlight[user]
if (color || type) {
state.highlight[user] = { color: color || data.color, type: type || data.type }
} else {
delete state.highlight[user]
}
}
},
actions: {
syncSettings: (store) => {
store.commit('setOption', { name: 'profileVersion', value: store.state.profileVersion + 1 })
const notice = {
level: 'info',
messageKey: 'settings_profile.synchronizing',
messageArgs: { profile: store.state.profile },
timeout: 5000
}
store.dispatch('pushGlobalNotice', notice)
store.rootState.api.backendInteractor.saveSettingsProfile({
settings: store.state, profileName: store.state.profile, version: store.state.profileVersion
}).then(() => {
store.dispatch('removeGlobalNotice', notice)
store.dispatch('pushGlobalNotice', {
level: 'success',
messageKey: 'settings_profile.synchronized',
messageArgs: { profile: store.state.profile },
timeout: 2000
})
store.dispatch('listSettingsProfiles')
}).catch((err) => {
store.dispatch('removeGlobalNotice', notice)
store.dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'settings_profile.synchronization_error',
messageArgs: { error: err.message },
timeout: 5000
})
console.error(err)
})
},
deleteSettingsProfile (store, name) {
store.rootState.api.backendInteractor.deleteSettingsProfile({ profileName: name }).then(() => {
store.dispatch('listSettingsProfiles')
})
},
loadSettings ({ dispatch }, data) {
const knownKeys = new Set(Object.keys(defaultState))
const presentKeys = new Set(Object.keys(data))
const intersection = new Set()
for (let elem of presentKeys) {
if (knownKeys.has(elem)) {
intersection.add(elem)
}
}
intersection.forEach(
name => dispatch('setOption', { name, value: data[name] })
)
},
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
setOption ({ commit, dispatch }, { name, value, manual }) {
commit('setOption', { name, value })
if (manual === true) {
dispatch('syncSettings')
}
switch (name) {
case 'theme':
setPreset(value)
break
case 'customTheme':
case 'customThemeSource':
applyTheme(value)
break
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value), {sameSite: 'Lax'})
dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
break
case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined)
break
}
},
getSettingsProfile (store, forceUpdate = false) {
const profile = store.state.profile
store.rootState.api.backendInteractor.getSettingsProfile({ store, profileName: profile })
.then(({ settings, version }) => {
console.log('found settings version', version)
if (forceUpdate || (version > store.state.profileVersion)) {
store.commit('setOption', { name: 'profileVersion', value: version })
Object.entries(settings).forEach(([name, value]) => {
if (store.state[name] !== value) {
store.dispatch('setOption', { name, value })
}
})
} else {
console.log('settings are up to date')
}
})
.catch((err) => {
console.error(`could not fetch profile ${profile}`, err)
if (err.statusCode === 404) {
// create profile
store.dispatch('pushGlobalNotice', {
level: 'warning',
messageKey: 'settings_profile.creating',
messageArgs: { profile },
timeout: 5000
})
store.dispatch('syncSettings')
}
})
}
}
}
export default config