lint fix wip
This commit is contained in:
parent
bb5cfa758c
commit
9e3d2c8742
@ -72,13 +72,15 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
|
|
||||||
function handleDragStart(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
function handleDragStart(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
||||||
setDraggedId(id)
|
setDraggedId(id)
|
||||||
e.dataTransfer.effectAllowed = 'move'
|
const { dataTransfer } = e
|
||||||
e.dataTransfer.setData('text/plain', id)
|
dataTransfer.effectAllowed = 'move'
|
||||||
|
dataTransfer.setData('text/plain', id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragOver(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
function handleDragOver(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.dataTransfer.dropEffect = 'move'
|
const { dataTransfer } = e
|
||||||
|
dataTransfer.dropEffect = 'move'
|
||||||
setDragOverId(id)
|
setDragOverId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -95,13 +95,15 @@ export function RelayManager({ onConfigChange }: RelayManagerProps): React.React
|
|||||||
|
|
||||||
function handleDragStart(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
function handleDragStart(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
||||||
setDraggedId(id)
|
setDraggedId(id)
|
||||||
e.dataTransfer.effectAllowed = 'move'
|
const { dataTransfer } = e
|
||||||
e.dataTransfer.setData('text/plain', id)
|
dataTransfer.effectAllowed = 'move'
|
||||||
|
dataTransfer.setData('text/plain', id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragOver(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
function handleDragOver(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.dataTransfer.dropEffect = 'move'
|
const { dataTransfer } = e
|
||||||
|
dataTransfer.dropEffect = 'move'
|
||||||
setDragOverId(id)
|
setDragOverId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -319,23 +319,30 @@ export async function fetchAuthorPresentationFromPool(
|
|||||||
|
|
||||||
// Cache the result if found
|
// Cache the result if found
|
||||||
if (value && events.length > 0) {
|
if (value && events.length > 0) {
|
||||||
const event = events.find(e => e.id === value.id) || events[0]
|
const event = events.find((e) => e.id === value.id) ?? events[0]
|
||||||
if (event) {
|
if (event) {
|
||||||
const tags = extractTagsFromEvent(event)
|
const tags = extractTagsFromEvent(event)
|
||||||
if (value.hash) {
|
if (value.hash) {
|
||||||
// Calculate totalSponsoring from cache before storing
|
// Calculate totalSponsoring from cache before storing
|
||||||
const { getAuthorSponsoring } = await import('./sponsoring')
|
const { getAuthorSponsoring } = await import('./sponsoring')
|
||||||
value.totalSponsoring = await getAuthorSponsoring(value.pubkey)
|
const totalSponsoring = await getAuthorSponsoring(value.pubkey)
|
||||||
|
const cachedValue: import('@/types/nostr').AuthorPresentationArticle = {
|
||||||
|
...value,
|
||||||
|
totalSponsoring,
|
||||||
|
}
|
||||||
const { writeObjectToCache } = await import('./helpers/writeObjectHelper')
|
const { writeObjectToCache } = await import('./helpers/writeObjectHelper')
|
||||||
await writeObjectToCache({
|
await writeObjectToCache({
|
||||||
objectType: 'author',
|
objectType: 'author',
|
||||||
hash: value.hash,
|
hash: value.hash,
|
||||||
event,
|
event,
|
||||||
parsed: value,
|
parsed: cachedValue,
|
||||||
version: tags.version,
|
version: tags.version,
|
||||||
hidden: tags.hidden,
|
hidden: tags.hidden,
|
||||||
index: value.index,
|
index: value.index,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
resolve(cachedValue)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,27 +52,38 @@ function setupContentDeliveryHandlers(
|
|||||||
finalize: (result: ContentDeliveryStatus) => void,
|
finalize: (result: ContentDeliveryStatus) => void,
|
||||||
isResolved: () => boolean
|
isResolved: () => boolean
|
||||||
): void {
|
): void {
|
||||||
|
let currentStatus = status
|
||||||
|
|
||||||
sub.on('event', (event: Event) => {
|
sub.on('event', (event: Event) => {
|
||||||
status.published = true
|
currentStatus = {
|
||||||
status.verifiedOnRelay = true
|
...currentStatus,
|
||||||
status.messageEventId = event.id
|
published: true,
|
||||||
status.retrievable = true
|
verifiedOnRelay: true,
|
||||||
finalize(status)
|
messageEventId: event.id,
|
||||||
|
retrievable: true,
|
||||||
|
}
|
||||||
|
finalize(currentStatus)
|
||||||
})
|
})
|
||||||
|
|
||||||
sub.on('eose', () => {
|
sub.on('eose', () => {
|
||||||
if (!status.published) {
|
if (!currentStatus.published) {
|
||||||
status.error = 'Message not found on relay'
|
currentStatus = {
|
||||||
|
...currentStatus,
|
||||||
|
error: 'Message not found on relay',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finalize(status)
|
finalize(currentStatus)
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!isResolved()) {
|
if (!isResolved()) {
|
||||||
if (!status.published) {
|
if (!currentStatus.published) {
|
||||||
status.error = 'Timeout waiting for message verification'
|
currentStatus = {
|
||||||
|
...currentStatus,
|
||||||
|
error: 'Timeout waiting for message verification',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finalize(status)
|
finalize(currentStatus)
|
||||||
}
|
}
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,16 +116,17 @@ function handleCodeBlock(
|
|||||||
state: RenderState,
|
state: RenderState,
|
||||||
elements: React.ReactElement[]
|
elements: React.ReactElement[]
|
||||||
): void {
|
): void {
|
||||||
|
const nextState = state
|
||||||
if (state.inCodeBlock) {
|
if (state.inCodeBlock) {
|
||||||
elements.push(
|
elements.push(
|
||||||
<pre key={`code-${index}`} className="bg-cyber-darker border border-neon-cyan/20 p-4 rounded-lg overflow-x-auto my-4 text-neon-cyan font-mono text-sm">
|
<pre key={`code-${index}`} className="bg-cyber-darker border border-neon-cyan/20 p-4 rounded-lg overflow-x-auto my-4 text-neon-cyan font-mono text-sm">
|
||||||
<code>{state.codeBlockContent.join('\n')}</code>
|
<code>{state.codeBlockContent.join('\n')}</code>
|
||||||
</pre>
|
</pre>
|
||||||
)
|
)
|
||||||
state.codeBlockContent = []
|
nextState.codeBlockContent = []
|
||||||
state.inCodeBlock = false
|
nextState.inCodeBlock = false
|
||||||
} else {
|
} else {
|
||||||
state.inCodeBlock = true
|
nextState.inCodeBlock = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +136,7 @@ function closeListIfNeeded(
|
|||||||
state: RenderState,
|
state: RenderState,
|
||||||
elements: React.ReactElement[]
|
elements: React.ReactElement[]
|
||||||
): void {
|
): void {
|
||||||
|
const nextState = state
|
||||||
if (state.currentList.length > 0 && !line.startsWith('- ') && !line.startsWith('* ') && line.trim() !== '') {
|
if (state.currentList.length > 0 && !line.startsWith('- ') && !line.startsWith('* ') && line.trim() !== '') {
|
||||||
elements.push(
|
elements.push(
|
||||||
<ul key={`list-${index}`} className="list-disc list-inside mb-4 space-y-1 text-cyber-accent marker:text-neon-cyan">
|
<ul key={`list-${index}`} className="list-disc list-inside mb-4 space-y-1 text-cyber-accent marker:text-neon-cyan">
|
||||||
@ -143,7 +145,7 @@ function closeListIfNeeded(
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)
|
)
|
||||||
state.currentList = []
|
nextState.currentList = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
lib/nip95.ts
19
lib/nip95.ts
@ -9,6 +9,19 @@ const MAX_VIDEO_BYTES = 45 * 1024 * 1024
|
|||||||
const IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp']
|
const IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp']
|
||||||
const VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/quicktime']
|
const VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/quicktime']
|
||||||
|
|
||||||
|
interface UnlockRequiredError extends Error {
|
||||||
|
unlockRequired: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUnlockRequiredError(): UnlockRequiredError {
|
||||||
|
const error = Object.assign(new Error('UNLOCK_REQUIRED'), { unlockRequired: true as const })
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnlockRequiredError(error: Error): error is UnlockRequiredError {
|
||||||
|
return (error as Partial<UnlockRequiredError>).unlockRequired === true
|
||||||
|
}
|
||||||
|
|
||||||
function assertBrowser(): void {
|
function assertBrowser(): void {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
throw new Error('NIP-95 upload is only available in the browser')
|
throw new Error('NIP-95 upload is only available in the browser')
|
||||||
@ -141,9 +154,7 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
|||||||
} else if (!isUnlocked) {
|
} else if (!isUnlocked) {
|
||||||
// Throw a special error that can be caught to trigger unlock modal
|
// Throw a special error that can be caught to trigger unlock modal
|
||||||
// This error should propagate to the caller, not be caught here
|
// This error should propagate to the caller, not be caught here
|
||||||
const unlockError = new Error('UNLOCK_REQUIRED')
|
throw createUnlockRequiredError()
|
||||||
;(unlockError as any).unlockRequired = true
|
|
||||||
throw unlockError
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('NIP-98 authentication required for nostrcheck.me but not available. Skipping endpoint.')
|
console.warn('NIP-98 authentication required for nostrcheck.me but not available. Skipping endpoint.')
|
||||||
// Skip this endpoint
|
// Skip this endpoint
|
||||||
@ -179,7 +190,7 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
|||||||
const errorMessage = error.message
|
const errorMessage = error.message
|
||||||
|
|
||||||
// If unlock is required, propagate the error immediately
|
// If unlock is required, propagate the error immediately
|
||||||
if (errorMessage === 'UNLOCK_REQUIRED' || (error as any).unlockRequired) {
|
if (errorMessage === 'UNLOCK_REQUIRED' || isUnlockRequiredError(error)) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,25 @@
|
|||||||
import type { TagType, TagCategory } from './nostrTagSystemTypes'
|
import type { TagType, TagCategory } from './nostrTagSystemTypes'
|
||||||
|
|
||||||
export function addSimpleTagFilter(filter: Record<string, string[] | number[]>, tagName: string, condition: boolean): void {
|
export function addSimpleTagFilter(
|
||||||
|
filter: Record<string, string[] | number[]>,
|
||||||
|
tagName: string,
|
||||||
|
condition: boolean
|
||||||
|
): Record<string, string[] | number[]> {
|
||||||
if (condition) {
|
if (condition) {
|
||||||
filter[`#${tagName}`] = ['']
|
return { ...filter, [`#${tagName}`]: [''] }
|
||||||
}
|
}
|
||||||
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addValueTagFilter(filter: Record<string, string[] | number[]>, tagName: string, value: string | undefined): void {
|
export function addValueTagFilter(
|
||||||
|
filter: Record<string, string[] | number[]>,
|
||||||
|
tagName: string,
|
||||||
|
value: string | undefined
|
||||||
|
): Record<string, string[] | number[]> {
|
||||||
if (value) {
|
if (value) {
|
||||||
filter[`#${tagName}`] = [value]
|
return { ...filter, [`#${tagName}`]: [value] }
|
||||||
}
|
}
|
||||||
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTagFilter(params: {
|
export function buildTagFilter(params: {
|
||||||
@ -23,7 +33,7 @@ export function buildTagFilter(params: {
|
|||||||
articleId?: string
|
articleId?: string
|
||||||
authorPubkey?: string
|
authorPubkey?: string
|
||||||
}): Record<string, string[] | number[]> {
|
}): Record<string, string[] | number[]> {
|
||||||
const filter: Record<string, string[] | number[]> = {
|
let filter: Record<string, string[] | number[]> = {
|
||||||
kinds: [1], // All are kind 1 notes
|
kinds: [1], // All are kind 1 notes
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,12 +43,12 @@ export function buildTagFilter(params: {
|
|||||||
if (params.category) {
|
if (params.category) {
|
||||||
filter[`#${params.category}`] = ['']
|
filter[`#${params.category}`] = ['']
|
||||||
}
|
}
|
||||||
addValueTagFilter(filter, 'id', params.id)
|
filter = addValueTagFilter(filter, 'id', params.id)
|
||||||
addValueTagFilter(filter, 'service', params.service)
|
filter = addValueTagFilter(filter, 'service', params.service)
|
||||||
addSimpleTagFilter(filter, 'paywall', params.paywall === true)
|
filter = addSimpleTagFilter(filter, 'paywall', params.paywall === true)
|
||||||
addSimpleTagFilter(filter, 'payment', params.payment === true)
|
filter = addSimpleTagFilter(filter, 'payment', params.payment === true)
|
||||||
addValueTagFilter(filter, 'series', params.seriesId)
|
filter = addValueTagFilter(filter, 'series', params.seriesId)
|
||||||
addValueTagFilter(filter, 'article', params.articleId)
|
filter = addValueTagFilter(filter, 'article', params.articleId)
|
||||||
|
|
||||||
if (params.authorPubkey) {
|
if (params.authorPubkey) {
|
||||||
filter.authors = [params.authorPubkey]
|
filter.authors = [params.authorPubkey]
|
||||||
|
|||||||
@ -138,9 +138,9 @@ class PublishWorkerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process all unpublished objects
|
// Process all unpublished objects
|
||||||
const objectsToProcess = Array.from(this.unpublishedObjects.values())
|
const objectsToProcess = Array.from(this.unpublishedObjects.entries())
|
||||||
for (const obj of objectsToProcess) {
|
for (const [key, obj] of objectsToProcess) {
|
||||||
await this.attemptPublish(obj)
|
await this.attemptPublish({ key, obj })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PublishWorker] Error processing unpublished objects:', error)
|
console.error('[PublishWorker] Error processing unpublished objects:', error)
|
||||||
@ -153,7 +153,8 @@ class PublishWorkerService {
|
|||||||
* Attempt to publish an unpublished object
|
* Attempt to publish an unpublished object
|
||||||
* Uses websocketService to route events to Service Worker
|
* Uses websocketService to route events to Service Worker
|
||||||
*/
|
*/
|
||||||
private async attemptPublish(obj: UnpublishedObject): Promise<void> {
|
private async attemptPublish(params: { key: string; obj: UnpublishedObject }): Promise<void> {
|
||||||
|
const {obj} = params
|
||||||
try {
|
try {
|
||||||
const { websocketService } = await import('./websocketService')
|
const { websocketService } = await import('./websocketService')
|
||||||
|
|
||||||
@ -207,26 +208,31 @@ class PublishWorkerService {
|
|||||||
await writeService.updatePublished(obj.objectType, obj.id, successfulRelays)
|
await writeService.updatePublished(obj.objectType, obj.id, successfulRelays)
|
||||||
console.warn(`[PublishWorker] Successfully published ${obj.objectType}:${obj.id} to ${successfulRelays.length} relay(s)`)
|
console.warn(`[PublishWorker] Successfully published ${obj.objectType}:${obj.id} to ${successfulRelays.length} relay(s)`)
|
||||||
// Remove from unpublished map
|
// Remove from unpublished map
|
||||||
this.unpublishedObjects.delete(`${obj.objectType}:${obj.id}`)
|
this.unpublishedObjects.delete(params.key)
|
||||||
} else {
|
} else {
|
||||||
// All relays failed, increment retry count
|
const current = this.unpublishedObjects.get(params.key)
|
||||||
obj.retryCount++
|
const next = current
|
||||||
obj.lastRetryAt = Date.now()
|
? { ...current, retryCount: current.retryCount + 1, lastRetryAt: Date.now() }
|
||||||
console.warn(`[PublishWorker] All relays failed for ${obj.objectType}:${obj.id}, retry count: ${obj.retryCount}/${MAX_RETRIES_PER_OBJECT}`)
|
: { ...obj, retryCount: obj.retryCount + 1, lastRetryAt: Date.now() }
|
||||||
|
this.unpublishedObjects.set(params.key, next)
|
||||||
|
|
||||||
|
console.warn(`[PublishWorker] All relays failed for ${obj.objectType}:${obj.id}, retry count: ${next.retryCount}/${MAX_RETRIES_PER_OBJECT}`)
|
||||||
|
|
||||||
// Remove if max retries reached
|
// Remove if max retries reached
|
||||||
if (obj.retryCount >= MAX_RETRIES_PER_OBJECT) {
|
if (next.retryCount >= MAX_RETRIES_PER_OBJECT) {
|
||||||
this.unpublishedObjects.delete(`${obj.objectType}:${obj.id}`)
|
this.unpublishedObjects.delete(params.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[PublishWorker] Error publishing ${obj.objectType}:${obj.id}:`, error)
|
console.error(`[PublishWorker] Error publishing ${obj.objectType}:${obj.id}:`, error)
|
||||||
// Increment retry count on error
|
const current = this.unpublishedObjects.get(params.key)
|
||||||
obj.retryCount++
|
const next = current
|
||||||
obj.lastRetryAt = Date.now()
|
? { ...current, retryCount: current.retryCount + 1, lastRetryAt: Date.now() }
|
||||||
|
: { ...params.obj, retryCount: params.obj.retryCount + 1, lastRetryAt: Date.now() }
|
||||||
|
this.unpublishedObjects.set(params.key, next)
|
||||||
|
|
||||||
if (obj.retryCount >= MAX_RETRIES_PER_OBJECT) {
|
if (next.retryCount >= MAX_RETRIES_PER_OBJECT) {
|
||||||
this.unpublishedObjects.delete(`${obj.objectType}:${obj.id}`)
|
this.unpublishedObjects.delete(params.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,17 @@ interface SWResponse {
|
|||||||
data?: unknown
|
data?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === 'object' && value !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSWResponse(value: unknown): value is SWResponse {
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return typeof value.type === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
class ServiceWorkerClient {
|
class ServiceWorkerClient {
|
||||||
private registration: ServiceWorkerRegistration | null = null
|
private registration: ServiceWorkerRegistration | null = null
|
||||||
private messageHandlers: Map<string, Array<(data: unknown) => void>> = new Map()
|
private messageHandlers: Map<string, Array<(data: unknown) => void>> = new Map()
|
||||||
@ -36,7 +47,7 @@ class ServiceWorkerClient {
|
|||||||
console.warn('[SWClient] Service Worker registered:', registration.scope)
|
console.warn('[SWClient] Service Worker registered:', registration.scope)
|
||||||
|
|
||||||
// Listen for messages from Service Worker
|
// Listen for messages from Service Worker
|
||||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
navigator.serviceWorker.addEventListener('message', (event: MessageEvent<unknown>) => {
|
||||||
this.handleMessage(event.data)
|
this.handleMessage(event.data)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -110,8 +121,12 @@ class ServiceWorkerClient {
|
|||||||
reject(new Error('Service Worker response timeout'))
|
reject(new Error('Service Worker response timeout'))
|
||||||
}, timeout)
|
}, timeout)
|
||||||
|
|
||||||
messageChannel.port1.onmessage = (event) => {
|
messageChannel.port1.onmessage = (event: MessageEvent<unknown>) => {
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
|
if (!isSWResponse(event.data)) {
|
||||||
|
reject(new Error('Invalid Service Worker response format'))
|
||||||
|
return
|
||||||
|
}
|
||||||
resolve(event.data)
|
resolve(event.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +139,11 @@ class ServiceWorkerClient {
|
|||||||
/**
|
/**
|
||||||
* Handle messages from Service Worker
|
* Handle messages from Service Worker
|
||||||
*/
|
*/
|
||||||
private handleMessage(message: SWResponse): void {
|
private handleMessage(message: unknown): void {
|
||||||
|
if (!isSWResponse(message)) {
|
||||||
|
console.warn('[SWClient] Ignoring invalid message from Service Worker', { message })
|
||||||
|
return
|
||||||
|
}
|
||||||
const handlers = this.messageHandlers.get(message.type)
|
const handlers = this.messageHandlers.get(message.type)
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
handlers.forEach((handler) => {
|
handlers.forEach((handler) => {
|
||||||
|
|||||||
@ -35,6 +35,43 @@ interface LogPublicationParams {
|
|||||||
objectId?: string
|
objectId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WorkerMessageEnvelope {
|
||||||
|
type: string
|
||||||
|
data?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkerErrorData {
|
||||||
|
originalType: string | undefined
|
||||||
|
taskId: string | undefined
|
||||||
|
error: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === 'object' && value !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWorkerMessageEnvelope(value: unknown): value is WorkerMessageEnvelope {
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return typeof value.type === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
function readString(value: unknown): string | undefined {
|
||||||
|
return typeof value === 'string' ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function readWorkerErrorData(value: unknown): WorkerErrorData {
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
return { originalType: undefined, taskId: undefined, error: undefined }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
originalType: readString(value.originalType),
|
||||||
|
taskId: readString(value.taskId),
|
||||||
|
error: readString(value.error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WriteService {
|
class WriteService {
|
||||||
private writeWorker: Worker | null = null
|
private writeWorker: Worker | null = null
|
||||||
private initPromise: Promise<void> | null = null
|
private initPromise: Promise<void> | null = null
|
||||||
@ -74,10 +111,13 @@ class WriteService {
|
|||||||
// Worker dans public/ pour Next.js
|
// Worker dans public/ pour Next.js
|
||||||
this.writeWorker = new Worker('/writeWorker.js', { type: 'classic' })
|
this.writeWorker = new Worker('/writeWorker.js', { type: 'classic' })
|
||||||
|
|
||||||
this.writeWorker.addEventListener('message', (event) => {
|
this.writeWorker.addEventListener('message', (event: MessageEvent<unknown>) => {
|
||||||
const { type, data } = event.data
|
if (!isWorkerMessageEnvelope(event.data)) {
|
||||||
if (type === 'ERROR') {
|
console.error('[WriteService] Received invalid worker message envelope', { data: event.data })
|
||||||
console.error('[WriteService] Worker error:', data)
|
return
|
||||||
|
}
|
||||||
|
if (event.data.type === 'ERROR') {
|
||||||
|
console.error('[WriteService] Worker error:', event.data.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -100,8 +140,8 @@ class WriteService {
|
|||||||
}, 2000)
|
}, 2000)
|
||||||
|
|
||||||
// Le worker est prêt quand il répond
|
// Le worker est prêt quand il répond
|
||||||
const readyHandler = (event: MessageEvent): void => {
|
const readyHandler = (event: MessageEvent<unknown>): void => {
|
||||||
if (event.data?.type === 'WORKER_READY') {
|
if (isWorkerMessageEnvelope(event.data) && event.data.type === 'WORKER_READY') {
|
||||||
clearTimeout(readyTimeout)
|
clearTimeout(readyTimeout)
|
||||||
this.writeWorker?.removeEventListener('message', readyHandler)
|
this.writeWorker?.removeEventListener('message', readyHandler)
|
||||||
resolve()
|
resolve()
|
||||||
@ -133,15 +173,24 @@ class WriteService {
|
|||||||
}, 10000)
|
}, 10000)
|
||||||
|
|
||||||
const handler = (event: MessageEvent): void => {
|
const handler = (event: MessageEvent): void => {
|
||||||
const { type, data } = event.data
|
if (!isWorkerMessageEnvelope(event.data)) {
|
||||||
if (type === 'WRITE_OBJECT_SUCCESS' && data.hash === params.hash) {
|
return
|
||||||
|
}
|
||||||
|
const responseType = event.data.type
|
||||||
|
const responseData = event.data.data
|
||||||
|
|
||||||
|
if (responseType === 'WRITE_OBJECT_SUCCESS' && isRecord(responseData) && responseData.hash === params.hash) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this.writeWorker?.removeEventListener('message', handler)
|
this.writeWorker?.removeEventListener('message', handler)
|
||||||
resolve()
|
resolve()
|
||||||
} else if (type === 'ERROR' && data.originalType === 'WRITE_OBJECT') {
|
} else if (responseType === 'ERROR') {
|
||||||
|
const errorData = readWorkerErrorData(responseData)
|
||||||
|
if (errorData.originalType !== 'WRITE_OBJECT') {
|
||||||
|
return
|
||||||
|
}
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this.writeWorker?.removeEventListener('message', handler)
|
this.writeWorker?.removeEventListener('message', handler)
|
||||||
reject(new Error(data.error))
|
reject(new Error(errorData.error ?? 'Write worker error'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,15 +250,27 @@ class WriteService {
|
|||||||
}, 10000)
|
}, 10000)
|
||||||
|
|
||||||
const handler = (event: MessageEvent): void => {
|
const handler = (event: MessageEvent): void => {
|
||||||
const { type, data } = event.data
|
if (!isWorkerMessageEnvelope(event.data)) {
|
||||||
if (type === 'UPDATE_PUBLISHED_SUCCESS' && data.id === id) {
|
return
|
||||||
|
}
|
||||||
|
const responseType = event.data.type
|
||||||
|
const responseData = event.data.data
|
||||||
|
|
||||||
|
if (responseType === 'UPDATE_PUBLISHED_SUCCESS' && isRecord(responseData) && responseData.id === id) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this.writeWorker?.removeEventListener('message', handler)
|
this.writeWorker?.removeEventListener('message', handler)
|
||||||
resolve()
|
resolve()
|
||||||
} else if (type === 'ERROR' && (data.originalType === 'UPDATE_PUBLISHED' || data.taskId?.startsWith('UPDATE_PUBLISHED'))) {
|
} else if (responseType === 'ERROR') {
|
||||||
|
const errorData = readWorkerErrorData(responseData)
|
||||||
|
const {taskId} = errorData
|
||||||
|
const isUpdatePublished =
|
||||||
|
errorData.originalType === 'UPDATE_PUBLISHED' || (taskId !== undefined && taskId.startsWith('UPDATE_PUBLISHED'))
|
||||||
|
if (!isUpdatePublished) {
|
||||||
|
return
|
||||||
|
}
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this.writeWorker?.removeEventListener('message', handler)
|
this.writeWorker?.removeEventListener('message', handler)
|
||||||
reject(new Error(data.error))
|
reject(new Error(errorData.error ?? 'Write worker error'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,15 +308,27 @@ class WriteService {
|
|||||||
}, 10000)
|
}, 10000)
|
||||||
|
|
||||||
const handler = (event: MessageEvent): void => {
|
const handler = (event: MessageEvent): void => {
|
||||||
const { type: responseType, data: responseData } = event.data
|
if (!isWorkerMessageEnvelope(event.data)) {
|
||||||
if (responseType === 'CREATE_NOTIFICATION_SUCCESS' && responseData.eventId === params.eventId) {
|
return
|
||||||
|
}
|
||||||
|
const responseType = event.data.type
|
||||||
|
const responseData = event.data.data
|
||||||
|
|
||||||
|
if (responseType === 'CREATE_NOTIFICATION_SUCCESS' && isRecord(responseData) && responseData.eventId === params.eventId) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this.writeWorker?.removeEventListener('message', handler)
|
this.writeWorker?.removeEventListener('message', handler)
|
||||||
resolve()
|
resolve()
|
||||||
} else if (responseType === 'ERROR' && (responseData.originalType === 'CREATE_NOTIFICATION' || responseData.taskId?.startsWith('CREATE_NOTIFICATION'))) {
|
} else if (responseType === 'ERROR') {
|
||||||
|
const errorData = readWorkerErrorData(responseData)
|
||||||
|
const {taskId} = errorData
|
||||||
|
const isCreateNotification =
|
||||||
|
errorData.originalType === 'CREATE_NOTIFICATION' || (taskId !== undefined && taskId.startsWith('CREATE_NOTIFICATION'))
|
||||||
|
if (!isCreateNotification) {
|
||||||
|
return
|
||||||
|
}
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this.writeWorker?.removeEventListener('message', handler)
|
this.writeWorker?.removeEventListener('message', handler)
|
||||||
reject(new Error(responseData.error))
|
reject(new Error(errorData.error ?? 'Write worker error'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user