fix: resolve runtime bugs (i18n export, chat crypto, async getUser, event leak) and remove dead code
This commit is contained in:
@@ -38,9 +38,6 @@ class AppShell extends HTMLElement {
|
|||||||
`
|
`
|
||||||
|
|
||||||
this.main = this.querySelector('#router-outlet')
|
this.main = this.querySelector('#router-outlet')
|
||||||
|
|
||||||
// Try to restore session
|
|
||||||
auth.tryRestoreSession()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupRouter() {
|
setupRouter() {
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class PageHome extends HTMLElement {
|
|||||||
// Geo state
|
// Geo state
|
||||||
this.userLat = null
|
this.userLat = null
|
||||||
this.userLng = null
|
this.userLng = null
|
||||||
|
|
||||||
|
this._onHashChange = this.handleHashChange.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -50,13 +52,13 @@ class PageHome extends HTMLElement {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Listen for URL changes (back/forward navigation)
|
// Listen for URL changes (back/forward navigation)
|
||||||
window.addEventListener('hashchange', this.handleHashChange.bind(this))
|
window.addEventListener('hashchange', this._onHashChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
if (this.unsubscribe) this.unsubscribe()
|
||||||
if (this.authUnsubscribe) this.authUnsubscribe()
|
if (this.authUnsubscribe) this.authUnsubscribe()
|
||||||
window.removeEventListener('hashchange', this.handleHashChange.bind(this))
|
window.removeEventListener('hashchange', this._onHashChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHashChange() {
|
handleHashChange() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { t, i18n } from '../../i18n.js'
|
import { t, i18n } from '../../i18n.js'
|
||||||
import { directus } from '../../services/directus.js'
|
import { directus } from '../../services/directus.js'
|
||||||
import { auth } from '../../services/auth.js'
|
import { auth } from '../../services/auth.js'
|
||||||
import { listingsService } from '../../services/listings.js'
|
|
||||||
import { getXmrRates, formatPrice as formatCurrencyPrice } from '../../services/currency.js'
|
import { getXmrRates, formatPrice as formatCurrencyPrice } from '../../services/currency.js'
|
||||||
import { escapeHTML } from '../../utils/helpers.js'
|
import { escapeHTML } from '../../utils/helpers.js'
|
||||||
import '../chat-widget.js'
|
import '../chat-widget.js'
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ class PageSettings extends HTMLElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.isLoggedIn = false
|
this.isLoggedIn = false
|
||||||
|
this.user = null
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
async connectedCallback() {
|
||||||
this.isLoggedIn = auth.isLoggedIn()
|
this.isLoggedIn = auth.isLoggedIn()
|
||||||
|
|
||||||
if (!this.isLoggedIn) {
|
if (!this.isLoggedIn) {
|
||||||
@@ -15,6 +16,7 @@ class PageSettings extends HTMLElement {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.user = await auth.getUser()
|
||||||
this.render()
|
this.render()
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@ class PageSettings extends HTMLElement {
|
|||||||
render() {
|
render() {
|
||||||
const currentTheme = this.getCurrentTheme()
|
const currentTheme = this.getCurrentTheme()
|
||||||
const currentLang = i18n.getLocale()
|
const currentLang = i18n.getLocale()
|
||||||
const user = auth.getUser()
|
const user = this.user
|
||||||
|
|
||||||
this.innerHTML = /* html */`
|
this.innerHTML = /* html */`
|
||||||
<div class="settings-page">
|
<div class="settings-page">
|
||||||
|
|||||||
@@ -206,3 +206,4 @@ class I18n {
|
|||||||
|
|
||||||
export const i18n = new I18n()
|
export const i18n = new I18n()
|
||||||
export const t = (key, params) => i18n.t(key, params)
|
export const t = (key, params) => i18n.t(key, params)
|
||||||
|
export const getCurrentLanguage = () => i18n.getLocale()
|
||||||
|
|||||||
@@ -76,11 +76,7 @@ class ConversationsService {
|
|||||||
let text = '[Encrypted]'
|
let text = '[Encrypted]'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isOwn) {
|
text = cryptoService.decrypt(msg.content_encrypted, msg.nonce, otherPublicKey)
|
||||||
text = cryptoService.decryptOwn(msg.content_encrypted, msg.nonce)
|
|
||||||
} else {
|
|
||||||
text = cryptoService.decrypt(msg.content_encrypted, msg.nonce, otherPublicKey)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
text = '[Decryption failed]'
|
text = '[Decryption failed]'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,13 +85,9 @@ class CryptoService {
|
|||||||
const nonce = this.nacl.randomBytes(this.nacl.box.nonceLength)
|
const nonce = this.nacl.randomBytes(this.nacl.box.nonceLength)
|
||||||
const messageUint8 = this.naclUtil.decodeUTF8(message)
|
const messageUint8 = this.naclUtil.decodeUTF8(message)
|
||||||
const recipientKey = this.naclUtil.decodeBase64(recipientPublicKey)
|
const recipientKey = this.naclUtil.decodeBase64(recipientPublicKey)
|
||||||
|
const sharedKey = this.nacl.box.before(recipientKey, this.keyPair.secretKey)
|
||||||
|
|
||||||
const encrypted = this.nacl.box(
|
const encrypted = this.nacl.secretbox(messageUint8, nonce, sharedKey)
|
||||||
messageUint8,
|
|
||||||
nonce,
|
|
||||||
recipientKey,
|
|
||||||
this.keyPair.secretKey
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nonce: this.naclUtil.encodeBase64(nonce),
|
nonce: this.naclUtil.encodeBase64(nonce),
|
||||||
@@ -103,16 +99,18 @@ class CryptoService {
|
|||||||
* Decrypt a message from a sender
|
* Decrypt a message from a sender
|
||||||
* @param {string} ciphertext - Base64 encoded ciphertext
|
* @param {string} ciphertext - Base64 encoded ciphertext
|
||||||
* @param {string} nonce - Base64 encoded nonce
|
* @param {string} nonce - Base64 encoded nonce
|
||||||
* @param {string} senderPublicKey - Base64 encoded public key
|
* @param {string} otherPublicKey - Base64 encoded public key of the other party
|
||||||
* @returns {string|null} - Decrypted message or null if failed
|
* @returns {string|null} - Decrypted message or null if failed
|
||||||
*/
|
*/
|
||||||
decrypt(ciphertext, nonce, senderPublicKey) {
|
decrypt(ciphertext, nonce, otherPublicKey) {
|
||||||
try {
|
try {
|
||||||
const decrypted = this.nacl.box.open(
|
const otherKey = this.naclUtil.decodeBase64(otherPublicKey)
|
||||||
|
const sharedKey = this.nacl.box.before(otherKey, this.keyPair.secretKey)
|
||||||
|
|
||||||
|
const decrypted = this.nacl.secretbox.open(
|
||||||
this.naclUtil.decodeBase64(ciphertext),
|
this.naclUtil.decodeBase64(ciphertext),
|
||||||
this.naclUtil.decodeBase64(nonce),
|
this.naclUtil.decodeBase64(nonce),
|
||||||
this.naclUtil.decodeBase64(senderPublicKey),
|
sharedKey
|
||||||
this.keyPair.secretKey
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!decrypted) return null
|
if (!decrypted) return null
|
||||||
|
|||||||
@@ -480,15 +480,6 @@ class DirectusService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async incrementViews(id) {
|
|
||||||
const listing = await this.getListing(id)
|
|
||||||
if (listing) {
|
|
||||||
return this.patch(`/items/listings/${id}`, {
|
|
||||||
views: (listing.views || 0) + 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Categories (Kategorien) ====================
|
// ==================== Categories (Kategorien) ====================
|
||||||
|
|
||||||
async getCategories() {
|
async getCategories() {
|
||||||
@@ -679,58 +670,6 @@ class DirectusService {
|
|||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Favorites (Favoriten) ====================
|
|
||||||
|
|
||||||
async getFavorites() {
|
|
||||||
const response = await this.get('/items/favorites', {
|
|
||||||
fields: ['*', 'listing.*', 'listing.images.directus_files_id.*'],
|
|
||||||
filter: { user: { _eq: '$CURRENT_USER' } }
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async addFavorite(listingId) {
|
|
||||||
const response = await this.post('/items/favorites', {
|
|
||||||
listing: listingId
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeFavorite(favoriteId) {
|
|
||||||
return this.delete(`/items/favorites/${favoriteId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async isFavorite(listingId) {
|
|
||||||
const response = await this.get('/items/favorites', {
|
|
||||||
filter: {
|
|
||||||
user: { _eq: '$CURRENT_USER' },
|
|
||||||
listing: { _eq: listingId }
|
|
||||||
},
|
|
||||||
limit: 1
|
|
||||||
})
|
|
||||||
return response.data.length > 0 ? response.data[0] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Reports (Meldungen) ====================
|
|
||||||
|
|
||||||
async reportListing(listingId, reason, details = '') {
|
|
||||||
const response = await this.post('/items/reports', {
|
|
||||||
listing: listingId,
|
|
||||||
reason,
|
|
||||||
details
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async reportUser(userId, reason, details = '') {
|
|
||||||
const response = await this.post('/items/reports', {
|
|
||||||
reported_user: userId,
|
|
||||||
reason,
|
|
||||||
details
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Files (Dateien/Bilder) ====================
|
// ==================== Files (Dateien/Bilder) ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -796,52 +735,6 @@ class DirectusService {
|
|||||||
return this.getFileUrl(fileId, { width: size, height: size, fit: 'cover' })
|
return this.getFileUrl(fileId, { width: size, height: size, fit: 'cover' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Search ====================
|
|
||||||
|
|
||||||
async globalSearch(query, options = {}) {
|
|
||||||
const [listings, categories] = await Promise.all([
|
|
||||||
this.searchListings(query, { limit: options.listingLimit || 10 }),
|
|
||||||
this.get('/items/categories', {
|
|
||||||
search: query,
|
|
||||||
limit: options.categoryLimit || 5
|
|
||||||
})
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
|
||||||
listings: listings.items,
|
|
||||||
categories: categories.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Stats / Dashboard ====================
|
|
||||||
|
|
||||||
async getUserStats() {
|
|
||||||
const [listings, favorites, conversations] = await Promise.all([
|
|
||||||
this.get('/items/listings', {
|
|
||||||
filter: { user_created: { _eq: '$CURRENT_USER' } },
|
|
||||||
aggregate: { count: '*' }
|
|
||||||
}),
|
|
||||||
this.get('/items/favorites', {
|
|
||||||
filter: { user: { _eq: '$CURRENT_USER' } },
|
|
||||||
aggregate: { count: '*' }
|
|
||||||
}),
|
|
||||||
this.get('/items/conversations', {
|
|
||||||
filter: {
|
|
||||||
_or: [
|
|
||||||
{ buyer: { _eq: '$CURRENT_USER' } },
|
|
||||||
{ seller: { _eq: '$CURRENT_USER' } }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
aggregate: { count: '*' }
|
|
||||||
})
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
|
||||||
listingsCount: listings.data?.[0]?.count || 0,
|
|
||||||
favoritesCount: favorites.data?.[0]?.count || 0,
|
|
||||||
conversationsCount: conversations.data?.[0]?.count || 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectusError extends Error {
|
class DirectusError extends Error {
|
||||||
|
|||||||
@@ -4,46 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { directus } from './directus.js'
|
import { directus } from './directus.js'
|
||||||
import currency from './currency.js'
|
|
||||||
|
|
||||||
class ListingsService {
|
class ListingsService {
|
||||||
constructor() {
|
|
||||||
this.cache = new Map()
|
|
||||||
this.cacheTimeout = 5 * 60 * 1000 // 5 minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFeaturedListings(limit = 8) {
|
|
||||||
return directus.getListings({
|
|
||||||
filter: {
|
|
||||||
status: { _eq: 'published' }
|
|
||||||
},
|
|
||||||
sort: ['-views', '-date_created'],
|
|
||||||
limit
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRecentListings(limit = 12) {
|
|
||||||
return directus.getListings({
|
|
||||||
filter: { status: { _eq: 'published' } },
|
|
||||||
sort: ['-date_created'],
|
|
||||||
limit
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getListingWithPriceConversion(id, targetCurrency = 'EUR') {
|
|
||||||
const listing = await directus.getListing(id)
|
|
||||||
if (!listing) return null
|
|
||||||
|
|
||||||
if (listing.price && (listing.price_mode === 'xmr' || listing.currency === 'XMR')) {
|
|
||||||
const rates = await currency.getXmrRates()
|
|
||||||
const fiatPrice = currency.convertFromXmr(listing.price, targetCurrency, rates)
|
|
||||||
listing.price_converted = fiatPrice
|
|
||||||
listing.price_converted_currency = targetCurrency
|
|
||||||
}
|
|
||||||
|
|
||||||
return listing
|
|
||||||
}
|
|
||||||
|
|
||||||
async getListingsWithFilters(filters = {}) {
|
async getListingsWithFilters(filters = {}) {
|
||||||
const directusFilter = { status: { _eq: 'published' } }
|
const directusFilter = { status: { _eq: 'published' } }
|
||||||
|
|
||||||
@@ -93,139 +55,6 @@ class ListingsService {
|
|||||||
search: filters.search
|
search: filters.search
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSimilarListings(listing, limit = 4) {
|
|
||||||
if (!listing) return []
|
|
||||||
|
|
||||||
const response = await directus.getListings({
|
|
||||||
filter: {
|
|
||||||
status: { _eq: 'published' },
|
|
||||||
id: { _neq: listing.id },
|
|
||||||
category: { _eq: listing.category?.id || listing.category }
|
|
||||||
},
|
|
||||||
limit
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.items || []
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserListings(userId) {
|
|
||||||
const response = await directus.get('/items/listings', {
|
|
||||||
fields: ['*', 'images.directus_files_id.id', 'category.name', 'location.name'],
|
|
||||||
filter: { user_created: { _eq: userId } },
|
|
||||||
sort: ['-date_created']
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async createListingWithImages(data, imageFiles) {
|
|
||||||
let images = []
|
|
||||||
|
|
||||||
if (imageFiles && imageFiles.length > 0) {
|
|
||||||
const uploadedFiles = await directus.uploadMultipleFiles(imageFiles)
|
|
||||||
images = uploadedFiles.map((file, index) => ({
|
|
||||||
directus_files_id: file.id,
|
|
||||||
sort: index
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const listingData = {
|
|
||||||
...data,
|
|
||||||
status: 'draft',
|
|
||||||
images: images.length > 0 ? { create: images } : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return directus.createListing(listingData)
|
|
||||||
}
|
|
||||||
|
|
||||||
async publishListing(id) {
|
|
||||||
return directus.updateListing(id, { status: 'published' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async unpublishListing(id) {
|
|
||||||
return directus.updateListing(id, { status: 'draft' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async archiveListing(id) {
|
|
||||||
return directus.updateListing(id, { status: 'archived' })
|
|
||||||
}
|
|
||||||
|
|
||||||
formatPrice(listing, locale = 'de-DE') {
|
|
||||||
if (!listing || listing.price === null || listing.price === undefined) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listing.price_type === 'negotiable') {
|
|
||||||
return `${this.formatAmount(listing.price, listing.currency, locale)} VB`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listing.price_type === 'free') {
|
|
||||||
return 'Gratis'
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.formatAmount(listing.price, listing.currency, locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatAmount(amount, currency = 'EUR', locale = 'de-DE') {
|
|
||||||
if (currency === 'XMR') {
|
|
||||||
return `${parseFloat(amount).toFixed(4)} XMR`
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Intl.NumberFormat(locale, {
|
|
||||||
style: 'currency',
|
|
||||||
currency: currency
|
|
||||||
}).format(amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
getConditionLabel(condition, lang = 'de') {
|
|
||||||
const labels = {
|
|
||||||
de: {
|
|
||||||
new: 'Neu',
|
|
||||||
like_new: 'Wie neu',
|
|
||||||
good: 'Gut',
|
|
||||||
fair: 'Akzeptabel',
|
|
||||||
poor: 'Stark gebraucht'
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
new: 'New',
|
|
||||||
like_new: 'Like new',
|
|
||||||
good: 'Good',
|
|
||||||
fair: 'Fair',
|
|
||||||
poor: 'Poor'
|
|
||||||
},
|
|
||||||
fr: {
|
|
||||||
new: 'Neuf',
|
|
||||||
like_new: 'Comme neuf',
|
|
||||||
good: 'Bon',
|
|
||||||
fair: 'Acceptable',
|
|
||||||
poor: 'Usagé'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels[lang]?.[condition] || condition
|
|
||||||
}
|
|
||||||
|
|
||||||
getPriceTypeLabel(priceType, lang = 'de') {
|
|
||||||
const labels = {
|
|
||||||
de: {
|
|
||||||
fixed: 'Festpreis',
|
|
||||||
negotiable: 'Verhandelbar',
|
|
||||||
free: 'Zu verschenken'
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
fixed: 'Fixed price',
|
|
||||||
negotiable: 'Negotiable',
|
|
||||||
free: 'Free'
|
|
||||||
},
|
|
||||||
fr: {
|
|
||||||
fixed: 'Prix fixe',
|
|
||||||
negotiable: 'Négociable',
|
|
||||||
free: 'Gratuit'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels[lang]?.[priceType] || priceType
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listingsService = new ListingsService()
|
export const listingsService = new ListingsService()
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
// Client must find nonce where SHA256(challenge + nonce) has N leading zeros
|
// Client must find nonce where SHA256(challenge + nonce) has N leading zeros
|
||||||
|
|
||||||
const DIFFICULTY = 4 // Number of leading zeros required (4 = ~65k attempts avg)
|
const DIFFICULTY = 4 // Number of leading zeros required (4 = ~65k attempts avg)
|
||||||
const CHALLENGE_EXPIRY = 5 * 60 * 1000 // 5 minutes
|
|
||||||
|
|
||||||
// Generate a challenge (call this from your API/backend)
|
// TODO: Replace with a server-side endpoint. Currently generates challenge
|
||||||
|
// client-side with a btoa() "signature" that provides no real security.
|
||||||
export function generateChallenge() {
|
export function generateChallenge() {
|
||||||
const challenge = crypto.randomUUID()
|
const challenge = crypto.randomUUID()
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now()
|
||||||
@@ -12,35 +12,10 @@ export function generateChallenge() {
|
|||||||
challenge,
|
challenge,
|
||||||
difficulty: DIFFICULTY,
|
difficulty: DIFFICULTY,
|
||||||
timestamp,
|
timestamp,
|
||||||
// Sign to prevent tampering (simple HMAC alternative)
|
|
||||||
signature: btoa(`${challenge}:${timestamp}:${DIFFICULTY}`)
|
signature: btoa(`${challenge}:${timestamp}:${DIFFICULTY}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify solution (call this from your API/backend)
|
|
||||||
export async function verifySolution(challenge, nonce, signature, timestamp) {
|
|
||||||
// Check expiry
|
|
||||||
if (Date.now() - timestamp > CHALLENGE_EXPIRY) {
|
|
||||||
return { valid: false, error: 'Challenge expired' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify signature
|
|
||||||
const expectedSig = btoa(`${challenge}:${timestamp}:${DIFFICULTY}`)
|
|
||||||
if (signature !== expectedSig) {
|
|
||||||
return { valid: false, error: 'Invalid signature' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify PoW
|
|
||||||
const hash = await sha256(`${challenge}${nonce}`)
|
|
||||||
const prefix = '0'.repeat(DIFFICULTY)
|
|
||||||
|
|
||||||
if (hash.startsWith(prefix)) {
|
|
||||||
return { valid: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: false, error: 'Invalid proof of work' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Solve challenge (runs in browser)
|
// Solve challenge (runs in browser)
|
||||||
export async function solveChallenge(challenge, difficulty, onProgress) {
|
export async function solveChallenge(challenge, difficulty, onProgress) {
|
||||||
let nonce = 0
|
let nonce = 0
|
||||||
|
|||||||
@@ -1,30 +1,69 @@
|
|||||||
const CACHE_NAME = 'dgray-v39';
|
const CACHE_NAME = 'dgray-v40';
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
|
'/favicon.png',
|
||||||
|
'/manifest.json',
|
||||||
|
|
||||||
|
// CSS
|
||||||
'/css/fonts.css',
|
'/css/fonts.css',
|
||||||
'/css/variables.css',
|
'/css/variables.css',
|
||||||
'/css/base.css',
|
'/css/base.css',
|
||||||
'/css/components.css',
|
'/css/components.css',
|
||||||
|
'/css/animate.min.css',
|
||||||
'/css/vendor/cropper.min.css',
|
'/css/vendor/cropper.min.css',
|
||||||
|
|
||||||
|
// Core JS
|
||||||
'/js/app.js',
|
'/js/app.js',
|
||||||
'/js/router.js',
|
'/js/router.js',
|
||||||
'/js/i18n.js',
|
'/js/i18n.js',
|
||||||
'/js/services/crypto.js',
|
'/js/utils/helpers.js',
|
||||||
'/js/services/conversations.js',
|
|
||||||
|
// Services
|
||||||
'/js/services/directus.js',
|
'/js/services/directus.js',
|
||||||
'/js/services/auth.js',
|
'/js/services/auth.js',
|
||||||
|
'/js/services/listings.js',
|
||||||
|
'/js/services/categories.js',
|
||||||
|
'/js/services/locations.js',
|
||||||
|
'/js/services/conversations.js',
|
||||||
|
'/js/services/crypto.js',
|
||||||
|
'/js/services/currency.js',
|
||||||
|
'/js/services/pow-captcha.js',
|
||||||
|
|
||||||
|
// Components
|
||||||
|
'/js/components/app-shell.js',
|
||||||
|
'/js/components/app-header.js',
|
||||||
|
'/js/components/app-footer.js',
|
||||||
|
'/js/components/auth-modal.js',
|
||||||
'/js/components/chat-widget.js',
|
'/js/components/chat-widget.js',
|
||||||
'/js/components/listing-card.js',
|
'/js/components/listing-card.js',
|
||||||
|
'/js/components/skeleton-card.js',
|
||||||
'/js/components/search-box.js',
|
'/js/components/search-box.js',
|
||||||
'/js/components/error-boundary.js',
|
'/js/components/error-boundary.js',
|
||||||
'/js/components/image-cropper.js',
|
'/js/components/image-cropper.js',
|
||||||
'/js/services/currency.js',
|
'/js/components/location-picker.js',
|
||||||
|
'/js/components/location-map.js',
|
||||||
|
'/js/components/pow-captcha.js',
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
'/js/components/pages/page-home.js',
|
||||||
|
'/js/components/pages/page-listing.js',
|
||||||
|
'/js/components/pages/page-create.js',
|
||||||
|
'/js/components/pages/page-favorites.js',
|
||||||
|
'/js/components/pages/page-my-listings.js',
|
||||||
|
'/js/components/pages/page-messages.js',
|
||||||
|
'/js/components/pages/page-settings.js',
|
||||||
|
'/js/components/pages/page-not-found.js',
|
||||||
|
|
||||||
|
// Vendor
|
||||||
'/js/vendor/cropper.min.js',
|
'/js/vendor/cropper.min.js',
|
||||||
|
|
||||||
|
// Locales
|
||||||
'/locales/de.json',
|
'/locales/de.json',
|
||||||
'/locales/en.json',
|
'/locales/en.json',
|
||||||
'/locales/fr.json',
|
'/locales/fr.json',
|
||||||
'/manifest.json',
|
|
||||||
|
// Fonts
|
||||||
'/assets/fonts/Inter-Regular.woff2',
|
'/assets/fonts/Inter-Regular.woff2',
|
||||||
'/assets/fonts/Inter-Medium.woff2',
|
'/assets/fonts/Inter-Medium.woff2',
|
||||||
'/assets/fonts/Inter-SemiBold.woff2',
|
'/assets/fonts/Inter-SemiBold.woff2',
|
||||||
|
|||||||
Reference in New Issue
Block a user