- Fix unused function warnings by renaming to _unusedExtractTags - Fix type errors in nostrTagSystem.ts for includes() calls - Fix type errors in reviews.ts for filter kinds array - Fix ArrayBuffer type errors in articleEncryption.ts - Remove unused imports (DecryptionKey, decryptArticleContent, extractTagsFromEvent) - All TypeScript checks now pass without disabling any controls
149 lines
4.0 KiB
TypeScript
149 lines
4.0 KiB
TypeScript
import type { AlbyInvoice } from '@/types/alby'
|
|
import { storageService } from './storage/indexedDB'
|
|
|
|
interface StoredArticleData {
|
|
content: string
|
|
authorPubkey: string
|
|
articleId: string
|
|
decryptionKey?: string
|
|
decryptionIV?: string
|
|
invoice: {
|
|
invoice: string
|
|
paymentHash: string
|
|
amount: number
|
|
expiresAt: number
|
|
} | null
|
|
createdAt: number
|
|
}
|
|
|
|
// Default expiration: 30 days in milliseconds
|
|
const DEFAULT_EXPIRATION = 30 * 24 * 60 * 60 * 1000
|
|
const MASTER_KEY_STORAGE_KEY = 'article_storage_master_key'
|
|
|
|
function getOrCreateMasterKey(): string {
|
|
if (typeof window === 'undefined') {
|
|
throw new Error('Storage encryption requires browser environment')
|
|
}
|
|
const existing = localStorage.getItem(MASTER_KEY_STORAGE_KEY)
|
|
if (existing) {
|
|
return existing
|
|
}
|
|
const keyBytes = crypto.getRandomValues(new Uint8Array(32))
|
|
let binary = ''
|
|
keyBytes.forEach((b) => {
|
|
binary += String.fromCharCode(b)
|
|
})
|
|
const key = btoa(binary)
|
|
localStorage.setItem(MASTER_KEY_STORAGE_KEY, key)
|
|
return key
|
|
}
|
|
|
|
function deriveSecret(articleId: string): string {
|
|
const masterKey = getOrCreateMasterKey()
|
|
return `${masterKey}:${articleId}`
|
|
}
|
|
|
|
/**
|
|
* Store private content temporarily until payment is confirmed
|
|
* Also stores the invoice if provided
|
|
* Uses IndexedDB exclusively
|
|
* Content expires after 30 days by default
|
|
* If decryptionKey and decryptionIV are provided, they will be stored for sending after payment
|
|
*/
|
|
export async function storePrivateContent(
|
|
articleId: string,
|
|
content: string,
|
|
authorPubkey: string,
|
|
invoice?: AlbyInvoice,
|
|
decryptionKey?: string,
|
|
decryptionIV?: string
|
|
): Promise<void> {
|
|
try {
|
|
const key = `article_private_content_${articleId}`
|
|
const secret = deriveSecret(articleId)
|
|
const data: StoredArticleData = {
|
|
content,
|
|
authorPubkey,
|
|
articleId,
|
|
...(decryptionKey ? { decryptionKey } : {}),
|
|
...(decryptionIV ? { decryptionIV } : {}),
|
|
invoice: invoice
|
|
? {
|
|
invoice: invoice.invoice,
|
|
paymentHash: invoice.paymentHash,
|
|
amount: invoice.amount,
|
|
expiresAt: invoice.expiresAt,
|
|
}
|
|
: null,
|
|
createdAt: Date.now(),
|
|
}
|
|
|
|
// Store with expiration (30 days)
|
|
await storageService.set(key, data, secret, DEFAULT_EXPIRATION)
|
|
} catch (error) {
|
|
console.error('Error storing private content:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get stored private content for an article
|
|
* Returns null if not found or expired
|
|
*/
|
|
export async function getStoredPrivateContent(articleId: string): Promise<{
|
|
content: string
|
|
authorPubkey: string
|
|
decryptionKey?: string
|
|
decryptionIV?: string
|
|
invoice?: AlbyInvoice
|
|
} | null> {
|
|
try {
|
|
const key = `article_private_content_${articleId}`
|
|
const secret = deriveSecret(articleId)
|
|
const data = await storageService.get<StoredArticleData>(key, secret)
|
|
|
|
if (!data) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
content: data.content,
|
|
authorPubkey: data.authorPubkey,
|
|
...(data.decryptionKey ? { decryptionKey: data.decryptionKey } : {}),
|
|
...(data.decryptionIV ? { decryptionIV: data.decryptionIV } : {}),
|
|
...(data.invoice
|
|
? {
|
|
invoice: {
|
|
invoice: data.invoice.invoice,
|
|
paymentHash: data.invoice.paymentHash,
|
|
amount: data.invoice.amount,
|
|
expiresAt: data.invoice.expiresAt,
|
|
} as AlbyInvoice,
|
|
}
|
|
: {}),
|
|
}
|
|
} catch (error) {
|
|
console.error('Error retrieving private content:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get stored invoice for an article
|
|
*/
|
|
export async function getStoredInvoice(articleId: string): Promise<AlbyInvoice | null> {
|
|
const stored = await getStoredPrivateContent(articleId)
|
|
return stored?.invoice ?? null
|
|
}
|
|
|
|
/**
|
|
* Remove stored private content (after successful send or expiry)
|
|
*/
|
|
export async function removeStoredPrivateContent(articleId: string): Promise<void> {
|
|
try {
|
|
const key = `article_private_content_${articleId}`
|
|
await storageService.delete(key)
|
|
} catch (error) {
|
|
console.error('Error removing private content:', error)
|
|
}
|
|
}
|