mirror of
https://github.com/mmjee/Piped-Material.git
synced 2024-12-06 19:26:36 +01:00
EDS Base Implementation
This commit is contained in:
@@ -25,10 +25,13 @@
|
||||
"core-js": "^3.30.2",
|
||||
"dexie": "^3.2.3",
|
||||
"dompurify": "^2.4.5",
|
||||
"ethers": "^5",
|
||||
"futoin-hkdf": "^1.5.1",
|
||||
"i18n-iso-countries": "^6.8.0",
|
||||
"intl-messageformat": "^10.3.5",
|
||||
"intl-unofficial-duration-unit-format": "^3.1.0",
|
||||
"javascript-time-ago": "https://cf-ipfs.com/ipfs/QmPfsnf4YYW63ddBUVXkT9BNQyuGKMnzyFVSV2dcx3dpz1?filename=jta-2.5.9.tgz",
|
||||
"libeds": "https://cf-ipfs.com/ipfs/QmeLULpS42DupKxR4wQxLGxqN8ybhV1ZJBVSYwZ7uNJMeH?filename=libeds-0.1.2.tar.gz",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^3.0.8",
|
||||
"mux.js": "^5.14.1",
|
||||
|
||||
+91
-4
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-app :style="bgStyles">
|
||||
<v-app :style="bgStyles" v-if="initialized">
|
||||
<v-app-bar
|
||||
app
|
||||
dense
|
||||
@@ -123,6 +123,11 @@
|
||||
<router-view />
|
||||
</v-main>
|
||||
</v-app>
|
||||
<div class="loader-svg-appvue" v-else>
|
||||
<svg class="circular">
|
||||
<circle class="path" cx="50" cy="50" r="20" fill="none" stroke-width="5" stroke-miterlimit="10"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -135,15 +140,84 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.loader-svg-appvue {
|
||||
$width: 100px;
|
||||
$green: #008744;
|
||||
$blue: #0057e7;
|
||||
$red: #d62d20;
|
||||
$yellow: #ffa700;
|
||||
$white: #eee;
|
||||
position: absolute;
|
||||
width: $width;
|
||||
height: $width;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.circular {
|
||||
animation: rotate 2s linear infinite;
|
||||
height: $width;
|
||||
position: relative;
|
||||
width: $width;
|
||||
}
|
||||
|
||||
.path {
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dashoffset: 0;
|
||||
stroke: #b6463a;
|
||||
animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -35;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -124;
|
||||
}
|
||||
}
|
||||
@keyframes color {
|
||||
100%,
|
||||
0% {
|
||||
stroke: $red;
|
||||
}
|
||||
40% {
|
||||
stroke: $blue;
|
||||
}
|
||||
66% {
|
||||
stroke: $green;
|
||||
}
|
||||
80%,
|
||||
90% {
|
||||
stroke: $yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mdiBrightness6, mdiMagnify, mdiClose, mdiHome } from '@mdi/js'
|
||||
|
||||
import SearchMenu from '@/routes/SearchMenu'
|
||||
import { changeLocale, SUPPORTED_LANGUAGES } from '@/plugins/i18n'
|
||||
import AuthenticationModal from '@/components/AuthenticationModal'
|
||||
|
||||
import { COLOR_SCHEME_STATES } from '@/store/prefs-store'
|
||||
import { syncPMDB } from '@/store/watched-videos-db'
|
||||
|
||||
import { initializeEDS } from '@/plugins/eds'
|
||||
import { initializeLocalLocale, changeLocale, SUPPORTED_LANGUAGES } from '@/plugins/i18n'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
@@ -160,6 +234,7 @@ export default {
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
initialized: false,
|
||||
drawer: false,
|
||||
// Only used on phones
|
||||
searchMenuOpened: false,
|
||||
@@ -266,8 +341,20 @@ export default {
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$vuetify.rtl = this.$store.state.i18n.rtl
|
||||
this.syncDarkMode()
|
||||
initializeEDS()
|
||||
.then(() => Promise.all([
|
||||
syncPMDB(),
|
||||
this.$store.dispatch('prefs/loadState'),
|
||||
this.$store.dispatch('auth/initializeAuth'),
|
||||
initializeLocalLocale(),
|
||||
() => {
|
||||
this.$vuetify.rtl = this.$store.state.i18n.rtl
|
||||
},
|
||||
this.syncDarkMode()
|
||||
]))
|
||||
.then(() => {
|
||||
this.initialized = true
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -24,8 +24,12 @@
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('auth_dialog.title') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="username" :label="$t('auth_dialog.username')"/>
|
||||
<v-text-field type="password" v-model="password" :label="$t('auth_dialog.password')"/>
|
||||
<v-text-field v-model="username" :label="$t('auth_dialog.username')" />
|
||||
<v-alert color="grey">
|
||||
Suggested Username: <code>{{ suggestedUsername }}</code>
|
||||
<v-btn icon @click="username = suggestedUsername"><v-icon>{{ mdiCheckAll }}</v-icon></v-btn>
|
||||
</v-alert>
|
||||
<v-text-field type="text" :value="password" label="Password (Autogenerated from the EDS private key per instance)" readonly />
|
||||
<v-alert outlined color="error" v-if="error != null">{{ error }}</v-alert>
|
||||
<v-btn @click="login" :disabled="requestInProgress" :loading="requestInProgress" color="primary" outlined large>
|
||||
{{ $t('auth_dialog.log_in') }}
|
||||
@@ -66,7 +70,7 @@
|
||||
<v-btn @click="logOutAllDevices" outlined large>{{ $t('actions.logOutFromAllDevices') }}</v-btn>
|
||||
<v-divider class="my-2" />
|
||||
<h5 class="text-subtitle-1">{{ $t('actions.delAccountMessage') }}</h5>
|
||||
<v-text-field type="password" v-model="password" label="Password"/>
|
||||
<v-text-field type="text" :value="password" label="Password (Autogenerated from the EDS private key per instance)" readonly />
|
||||
<v-btn outlined style="display: inline-block; white-space: normal; height: auto; padding: 8px" small @click="delAccount">{{ $t('actions.confirmPWToDelAcc') }}</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -74,7 +78,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HKDF from 'futoin-hkdf'
|
||||
import { mdiCheckAll } from '@mdi/js'
|
||||
|
||||
import { AuthenticationError } from '@/store/authentication-store'
|
||||
import { EDS } from '@/plugins/eds'
|
||||
|
||||
export default {
|
||||
name: 'AuthenticationModal',
|
||||
@@ -84,9 +92,27 @@ export default {
|
||||
requestInProgress: false,
|
||||
dialogOpen: false,
|
||||
username: '',
|
||||
password: ''
|
||||
suggestedUsername: '',
|
||||
password: '',
|
||||
|
||||
mdiCheckAll
|
||||
}),
|
||||
watch: {
|
||||
apiURL: 'calcStuff'
|
||||
},
|
||||
mounted () {
|
||||
this.calcStuff()
|
||||
},
|
||||
computed: {
|
||||
apiURL () {
|
||||
return this.$store.getters['prefs/apiUrl']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
calcStuff () {
|
||||
this.password = HKDF.expand('sha512', (512 / 8), EDS.privateKey, 48, this.apiURL).toString('base64')
|
||||
this.suggestedUsername = HKDF.expand('sha512', (512 / 8), EDS.privateKey, 12, this.apiURL + '_username').toString('hex')
|
||||
},
|
||||
async doCall (path) {
|
||||
this.error = null
|
||||
try {
|
||||
|
||||
+8
-15
@@ -4,7 +4,7 @@ import App from './App.vue'
|
||||
import 'antar-hinted/antar.css'
|
||||
import '@/styles/main.scss'
|
||||
|
||||
import { i18n, i18nInitialized } from '@/plugins/i18n'
|
||||
import { i18n } from '@/plugins/i18n'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
@@ -12,17 +12,10 @@ import router from '@/router'
|
||||
import './registerServiceWorker'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Promise.all([
|
||||
store.dispatch('prefs/loadState'),
|
||||
store.dispatch('auth/initializeAuth'),
|
||||
i18nInitialized
|
||||
]).then(() => {
|
||||
new Vue({
|
||||
vuetify,
|
||||
i18n,
|
||||
store,
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
})
|
||||
new Vue({
|
||||
vuetify,
|
||||
i18n,
|
||||
store,
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { EncryptedDatabase } from 'libeds'
|
||||
import { providers as EthersProviders } from 'ethers'
|
||||
|
||||
export const EDS = new EncryptedDatabase()
|
||||
export function initializeEDS () {
|
||||
return EDS.initialize(new EthersProviders.Web3Provider(window.ethereum, 'any'), {
|
||||
appID: 'PipedMaterial',
|
||||
url: process.env.VUE_APP_EDS_URL ?? 'wss://eds.gra.১.net:18001'
|
||||
})
|
||||
}
|
||||
|
||||
window.EDS = EDS
|
||||
+4
-5
@@ -3,6 +3,7 @@ import VueI18n from 'vue-i18n'
|
||||
import TimeAgo from 'javascript-time-ago'
|
||||
import * as LocaleMatcher from '@formatjs/intl-localematcher'
|
||||
|
||||
import { EDS } from '@/plugins/eds'
|
||||
import store from '@/store'
|
||||
import ENTranslations from '@/translations/en.json'
|
||||
import ENTimeAgo from 'javascript-time-ago/locale/en.json'
|
||||
@@ -187,15 +188,13 @@ export async function changeLocale (lang) {
|
||||
loadFormatting(lang)
|
||||
])
|
||||
i18n.locale = lang
|
||||
window.localStorage.setItem('LOCALE', lang)
|
||||
await EDS.setKey('locale', lang)
|
||||
}
|
||||
|
||||
function initializeLocalLocale () {
|
||||
let lang = window.localStorage.getItem('LOCALE')
|
||||
export async function initializeLocalLocale () {
|
||||
let lang = await EDS.getKey('locale')
|
||||
if (lang == null) {
|
||||
lang = LocaleMatcher.match(navigator.languages, SUPPORTED_LANGUAGES, 'en')
|
||||
}
|
||||
return changeLocale(lang)
|
||||
}
|
||||
|
||||
export const i18nInitialized = initializeLocalLocale()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from 'axios'
|
||||
import { isPlainObject as _isPlainObject, set as _set } from 'lodash-es'
|
||||
import { EDS } from '@/plugins/eds'
|
||||
|
||||
export class AuthenticationError extends Error {
|
||||
constructor (message) {
|
||||
@@ -28,14 +29,14 @@ const AuthenticationStore = {
|
||||
isAuthenticated: true,
|
||||
authToken: token
|
||||
})
|
||||
window.localStorage.setItem('AUTH', JSON.stringify(state.authStateByInstance))
|
||||
EDS.setKey('AUTH', state.authStateByInstance).catch(e => console.error(e))
|
||||
},
|
||||
|
||||
deleteAuthToken (state, { apiURL }) {
|
||||
_set(state.authStateByInstance, [apiURL], {
|
||||
isAuthenticated: false
|
||||
})
|
||||
window.localStorage.setItem('AUTH', JSON.stringify(state.authStateByInstance))
|
||||
EDS.setKey('AUTH', state.authStateByInstance).catch(e => console.error(e))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -52,10 +53,10 @@ const AuthenticationStore = {
|
||||
},
|
||||
|
||||
actions: {
|
||||
initializeAuth ({ commit }) {
|
||||
const data = window.localStorage.getItem('AUTH')
|
||||
async initializeAuth ({ commit }) {
|
||||
const data = await EDS.getKey('AUTH')
|
||||
if (data != null) {
|
||||
commit('replaceAuth', JSON.parse(data))
|
||||
commit('replaceAuth', data)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -139,15 +140,6 @@ const AuthenticationStore = {
|
||||
}
|
||||
}
|
||||
|
||||
function initializeAuthEvents (store) {
|
||||
window.addEventListener('storage', (storageEv) => {
|
||||
if (storageEv.key === 'AUTH') {
|
||||
store.commit('auth/replaceAuth', JSON.parse(storageEv.newValue))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
AuthenticationStore,
|
||||
initializeAuthEvents
|
||||
AuthenticationStore
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,8 +1,8 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import { PrefsStore, initializePrefEvents } from '@/store/prefs-store'
|
||||
import { AuthenticationStore, initializeAuthEvents } from '@/store/authentication-store'
|
||||
import { initializePrefEvents, PrefsStore } from '@/store/prefs-store'
|
||||
import { AuthenticationStore } from '@/store/authentication-store'
|
||||
import { i18nStore } from '@/store/i18n-store'
|
||||
|
||||
Vue.use(Vuex)
|
||||
@@ -16,6 +16,5 @@ const store = new Vuex.Store({
|
||||
})
|
||||
|
||||
initializePrefEvents(store)
|
||||
initializeAuthEvents(store)
|
||||
|
||||
export default store
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isString, set as _set } from 'lodash-es'
|
||||
import { EDS } from '@/plugins/eds'
|
||||
|
||||
export const COLOR_SCHEME_STATES = {
|
||||
LIGHT: 0,
|
||||
@@ -43,7 +44,7 @@ const PrefsStore = {
|
||||
value
|
||||
}) {
|
||||
_set(state.prefs, id, value)
|
||||
window.localStorage.setItem('PREFERENCES', JSON.stringify(state.prefs))
|
||||
EDS.setKey('preferences', state.prefs).catch(e => console.error(e))
|
||||
},
|
||||
|
||||
replacePrefs (state, nextPrefs) {
|
||||
@@ -51,17 +52,16 @@ const PrefsStore = {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
loadState ({ commit }) {
|
||||
async loadState ({ commit }) {
|
||||
try {
|
||||
const cs = window.localStorage.getItem('COLOR_SCHEME')
|
||||
if (isString(cs) && cs.length !== 0) {
|
||||
commit('setColorScheme', { colorScheme: JSON.parse(cs) })
|
||||
}
|
||||
|
||||
const jv = window.localStorage.getItem('PREFERENCES')
|
||||
if (isString(jv) && jv.length !== 0) {
|
||||
const p = JSON.parse(jv)
|
||||
commit('replacePrefs', p)
|
||||
const jv = await EDS.getKey('preferences')
|
||||
if (jv != null) {
|
||||
commit('replacePrefs', jv)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error:', e)
|
||||
@@ -113,9 +113,7 @@ const PrefsStore = {
|
||||
|
||||
function initializePrefEvents (store) {
|
||||
window.addEventListener('storage', (storageEv) => {
|
||||
if (storageEv.key === 'PREFERENCES') {
|
||||
store.commit('prefs/replacePrefs', JSON.parse(storageEv.newValue))
|
||||
} else if (storageEv.key === 'COLOR_SCHEME') {
|
||||
if (storageEv.key === 'COLOR_SCHEME') {
|
||||
store.commit('prefs/setColorScheme', JSON.parse(storageEv.newValue))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import Dexie from 'dexie'
|
||||
import { initializeDexieTables } from 'libeds/dexiesync'
|
||||
|
||||
import { EDS } from '@/plugins/eds'
|
||||
|
||||
export const PMDB = new Dexie('PipedMaterialDB')
|
||||
|
||||
@@ -6,6 +9,10 @@ PMDB.version(3).stores({
|
||||
watchedVideos: '++id,videoId,progressPcnt,timestamp'
|
||||
})
|
||||
|
||||
export async function syncPMDB () {
|
||||
return initializeDexieTables({ EDS, tableList: [PMDB.watchedVideos] })
|
||||
}
|
||||
|
||||
export async function addWatchedVideo (videoObj) {
|
||||
return PMDB.watchedVideos.add({
|
||||
videoId: videoObj.videoId,
|
||||
|
||||
Reference in New Issue
Block a user