fix: detect private browsing and safari blocked cookies (#733)
* WIP: detect private browsing and safari blocked cookies * just check for indexeddb * just check for indexeddb * change warning text * change text * change text again * change text again fixes #444
This commit is contained in:
parent
c0f857336a
commit
0e524f3e9a
|
@ -3,7 +3,17 @@
|
||||||
// the build process and write it to inline-script-checksum.json.
|
// the build process and write it to inline-script-checksum.json.
|
||||||
window.__themeColors = process.env.THEME_COLORS
|
window.__themeColors = process.env.THEME_COLORS
|
||||||
|
|
||||||
if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
|
const hasLocalStorage = (() => {
|
||||||
|
try {
|
||||||
|
// iOS safari throws here if cookies are disabled
|
||||||
|
let unused = localStorage.length // eslint-disable-line
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (hasLocalStorage && localStorage.store_currentInstance && localStorage.store_instanceThemes) {
|
||||||
let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
|
let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
|
||||||
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
|
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
|
||||||
if (theme && theme !== 'default') {
|
if (theme && theme !== 'default') {
|
||||||
|
@ -18,14 +28,14 @@ if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!localStorage.store_currentInstance) {
|
if (!hasLocalStorage || !localStorage.store_currentInstance) {
|
||||||
// if not logged in, show all these 'hidden-from-ssr' elements
|
// if not logged in, show all these 'hidden-from-ssr' elements
|
||||||
let style = document.createElement('style')
|
let style = document.createElement('style')
|
||||||
style.textContent = '.hidden-from-ssr { opacity: 1 !important; }'
|
style.textContent = '.hidden-from-ssr { opacity: 1 !important; }'
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localStorage.store_disableCustomScrollbars === 'true') {
|
if (hasLocalStorage && localStorage.store_disableCustomScrollbars === 'true') {
|
||||||
// if user has disabled custom scrollbars, remove this style
|
// if user has disabled custom scrollbars, remove this style
|
||||||
let theScrollbarStyle = document.getElementById('theScrollbarStyle')
|
let theScrollbarStyle = document.getElementById('theScrollbarStyle')
|
||||||
theScrollbarStyle.setAttribute('media', 'only x') // disables the style
|
theScrollbarStyle.setAttribute('media', 'only x') // disables the style
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -6003,6 +6003,11 @@
|
||||||
"json5": "^0.5.0"
|
"json5": "^0.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"localstorage-memory": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/localstorage-memory/-/localstorage-memory-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-t9P8WB6DcVttbw/W4PIE8HOqum8Qlvx5SjR6oInwR9Uia0EEmyUeBh7S+weKByW+l/f45Bj4L/dgZikGFDM6ng=="
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"idb-keyval": "^3.1.0",
|
"idb-keyval": "^3.1.0",
|
||||||
"indexeddb-getall-shim": "^1.3.5",
|
"indexeddb-getall-shim": "^1.3.5",
|
||||||
"intersection-observer": "^0.5.1",
|
"intersection-observer": "^0.5.1",
|
||||||
|
"localstorage-memory": "^1.0.3",
|
||||||
"lodash-es": "^4.17.11",
|
"lodash-es": "^4.17.11",
|
||||||
"lodash-webpack-plugin": "^0.11.5",
|
"lodash-webpack-plugin": "^0.11.5",
|
||||||
"mini-css-extract-plugin": "^0.4.5",
|
"mini-css-extract-plugin": "^0.4.5",
|
||||||
|
|
|
@ -3,10 +3,18 @@
|
||||||
|
|
||||||
<form class="add-new-instance" on:submit='onSubmit(event)' aria-labelledby="add-an-instance-h1">
|
<form class="add-new-instance" on:submit='onSubmit(event)' aria-labelledby="add-an-instance-h1">
|
||||||
|
|
||||||
|
{#if !hasIndexedDB}
|
||||||
|
<div class="form-error form-error-user-error" role="alert">
|
||||||
|
Your browser doesn't support IndexedDB, possibly because it's in private browsing mode
|
||||||
|
or blocking cookies. Pinafore stores all data locally, and requires IndexedDB to work
|
||||||
|
correctly.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $logInToInstanceError && $logInToInstanceErrorForText === $instanceNameInSearch}
|
{#if $logInToInstanceError && $logInToInstanceErrorForText === $instanceNameInSearch}
|
||||||
<div class="form-error form-error-user-error" role="alert">
|
<div class="form-error form-error-user-error" role="alert">
|
||||||
Error: {$logInToInstanceError}
|
Error: {$logInToInstanceError}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<noscript>
|
<noscript>
|
||||||
|
@ -69,19 +77,25 @@
|
||||||
import { store } from '../../../_store/store'
|
import { store } from '../../../_store/store'
|
||||||
import { logInToInstance, handleOauthCode } from '../../../_actions/addInstance'
|
import { logInToInstance, handleOauthCode } from '../../../_actions/addInstance'
|
||||||
import ExternalLink from '../../../_components/ExternalLink.html'
|
import ExternalLink from '../../../_components/ExternalLink.html'
|
||||||
|
import { testHasIndexedDB } from '../../../_utils/testStorage'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
let codeMatch = location.search.match(/code=([^&]+)/)
|
let codeMatch = location.search.match(/code=([^&]+)/)
|
||||||
if (codeMatch) {
|
if (codeMatch) {
|
||||||
handleOauthCode(codeMatch[1])
|
return handleOauthCode(codeMatch[1])
|
||||||
}
|
}
|
||||||
|
let hasIndexedDB = await testHasIndexedDB()
|
||||||
|
this.set({ hasIndexedDB })
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
SettingsLayout,
|
SettingsLayout,
|
||||||
ExternalLink
|
ExternalLink
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
data: () => ({
|
||||||
|
hasIndexedDB: true
|
||||||
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
onSubmit (event) {
|
onSubmit (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Store } from 'svelte/store'
|
import { Store } from 'svelte/store'
|
||||||
|
import { safeLocalStorage as LS } from '../_utils/safeLocalStorage'
|
||||||
|
|
||||||
let lifecycle
|
let lifecycle
|
||||||
if (process.browser) {
|
if (process.browser) {
|
||||||
lifecycle = require('page-lifecycle/dist/lifecycle.mjs').default
|
lifecycle = require('page-lifecycle/dist/lifecycle.mjs').default
|
||||||
}
|
}
|
||||||
|
|
||||||
const LS = process.browser && localStorage
|
|
||||||
|
|
||||||
function safeParse (str) {
|
function safeParse (str) {
|
||||||
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
|
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
|
||||||
}
|
}
|
||||||
|
|
6
routes/_utils/safeLocalStorage.js
Normal file
6
routes/_utils/safeLocalStorage.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import localStorageMemory from 'localstorage-memory'
|
||||||
|
import { testHasLocalStorage } from './testStorage'
|
||||||
|
|
||||||
|
const safeLocalStorage = testHasLocalStorage() ? localStorage : localStorageMemory
|
||||||
|
|
||||||
|
export { safeLocalStorage }
|
42
routes/_utils/testStorage.js
Normal file
42
routes/_utils/testStorage.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// LocalStorage and IDB may be disabled in private mode, when "blocking cookies" in Safari,
|
||||||
|
// or other cases
|
||||||
|
|
||||||
|
import { thunk } from './thunk'
|
||||||
|
|
||||||
|
const testKey = '__test__'
|
||||||
|
|
||||||
|
export const testHasLocalStorage = thunk(() => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(testKey, testKey)
|
||||||
|
if (!localStorage.length || localStorage.getItem(testKey) !== testKey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
localStorage.removeItem(testKey)
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
export const testHasIndexedDB = thunk(async () => {
|
||||||
|
if (typeof indexedDB === 'undefined') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let idbFailed = await new Promise(resolve => {
|
||||||
|
let db = indexedDB.open(testKey)
|
||||||
|
db.onerror = () => resolve(true)
|
||||||
|
db.onsuccess = () => {
|
||||||
|
indexedDB.deleteDatabase(testKey)
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (idbFailed) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
11
routes/_utils/thunk.js
Normal file
11
routes/_utils/thunk.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export function thunk (func) {
|
||||||
|
let cached
|
||||||
|
let runOnce
|
||||||
|
return () => {
|
||||||
|
if (!runOnce) {
|
||||||
|
cached = func()
|
||||||
|
runOnce = true
|
||||||
|
}
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,17 @@ html{scrollbar-face-color:var(--scrollbar-face-color);scrollbar-track-color:var(
|
||||||
// the build process and write it to inline-script-checksum.json.
|
// the build process and write it to inline-script-checksum.json.
|
||||||
window.__themeColors = {"default":"royalblue","scarlet":"#e04e41","seafoam":"#177380","hotpants":"hotpink","oaken":"saddlebrown","majesty":"blueviolet","gecko":"#4ab92f","ozark":"#5263af","cobalt":"#08439b","sorcery":"#ae91e8","punk":"#e04e41","riot":"hotpink","hacker":"#4ab92f","pitchblack":"#000"}
|
window.__themeColors = {"default":"royalblue","scarlet":"#e04e41","seafoam":"#177380","hotpants":"hotpink","oaken":"saddlebrown","majesty":"blueviolet","gecko":"#4ab92f","ozark":"#5263af","cobalt":"#08439b","sorcery":"#ae91e8","punk":"#e04e41","riot":"hotpink","hacker":"#4ab92f","pitchblack":"#000"}
|
||||||
|
|
||||||
if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
|
const hasLocalStorage = (() => {
|
||||||
|
try {
|
||||||
|
// iOS safari throws here if cookies are disabled
|
||||||
|
let unused = localStorage.length // eslint-disable-line
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (hasLocalStorage && localStorage.store_currentInstance && localStorage.store_instanceThemes) {
|
||||||
let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
|
let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
|
||||||
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
|
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
|
||||||
if (theme && theme !== 'default') {
|
if (theme && theme !== 'default') {
|
||||||
|
@ -67,14 +77,14 @@ if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!localStorage.store_currentInstance) {
|
if (!hasLocalStorage || !localStorage.store_currentInstance) {
|
||||||
// if not logged in, show all these 'hidden-from-ssr' elements
|
// if not logged in, show all these 'hidden-from-ssr' elements
|
||||||
let style = document.createElement('style')
|
let style = document.createElement('style')
|
||||||
style.textContent = '.hidden-from-ssr { opacity: 1 !important; }'
|
style.textContent = '.hidden-from-ssr { opacity: 1 !important; }'
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localStorage.store_disableCustomScrollbars === 'true') {
|
if (hasLocalStorage && localStorage.store_disableCustomScrollbars === 'true') {
|
||||||
// if user has disabled custom scrollbars, remove this style
|
// if user has disabled custom scrollbars, remove this style
|
||||||
let theScrollbarStyle = document.getElementById('theScrollbarStyle')
|
let theScrollbarStyle = document.getElementById('theScrollbarStyle')
|
||||||
theScrollbarStyle.setAttribute('media', 'only x') // disables the style
|
theScrollbarStyle.setAttribute('media', 'only x') // disables the style
|
||||||
|
|
Loading…
Reference in a new issue