This commit is contained in:
zwgczx committed 2026-04-07 16:27:49 +08:00
commit 6fdf336b9e
128 files changed
+36789

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
const PROJECT_EVENTS_CHANNEL = 'jgjs-project-events'
const PROJECT_DELETED_STORAGE_KEY = 'jgjs-project-event:project-deleted'
const RESET_ALL_STORAGE_KEY = 'jgjs-project-event:reset-all'
const PROJECT_EVENT_SESSION_ID_KEY = 'jgjs-project-event-session-id'
type ProjectDeletedPayload = {
type: 'project-deleted'
projectId: string
sourceSessionId: string
at: number
}
type ResetAllPayload = {
type: 'reset-all'
sourceSessionId: string
at: number
}
const randomSessionId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
const getOrCreateSessionId = () => {
try {
const existing = String(window.sessionStorage.getItem(PROJECT_EVENT_SESSION_ID_KEY) || '').trim()
if (existing) return existing
const next = randomSessionId()
window.sessionStorage.setItem(PROJECT_EVENT_SESSION_ID_KEY, next)
return next
} catch {
return randomSessionId()
}
}
const parseProjectDeletedPayload = (raw: string | null): ProjectDeletedPayload | null => {
if (!raw) return null
try {
const parsed = JSON.parse(raw) as Partial<ProjectDeletedPayload>
if (!parsed || typeof parsed !== 'object') return null
if (parsed.type !== 'project-deleted') return null
if (typeof parsed.projectId !== 'string' || !parsed.projectId.trim()) return null
if (typeof parsed.sourceSessionId !== 'string' || !parsed.sourceSessionId.trim()) return null
if (typeof parsed.at !== 'number' || !Number.isFinite(parsed.at)) return null
return {
type: 'project-deleted',
projectId: parsed.projectId.trim(),
sourceSessionId: parsed.sourceSessionId.trim(),
at: parsed.at
}
} catch {
return null
}
}
const parseResetAllPayload = (raw: string | null): ResetAllPayload | null => {
if (!raw) return null
try {
const parsed = JSON.parse(raw) as Partial<ResetAllPayload>
if (!parsed || typeof parsed !== 'object') return null
if (parsed.type !== 'reset-all') return null
if (typeof parsed.sourceSessionId !== 'string' || !parsed.sourceSessionId.trim()) return null
if (typeof parsed.at !== 'number' || !Number.isFinite(parsed.at)) return null
return {
type: 'reset-all',
sourceSessionId: parsed.sourceSessionId.trim(),
at: parsed.at
}
} catch {
return null
}
}
export const emitProjectDeleted = (projectIdRaw: string) => {
const projectId = String(projectIdRaw || '').trim()
if (!projectId) return
const payload: ProjectDeletedPayload = {
type: 'project-deleted',
projectId,
sourceSessionId: getOrCreateSessionId(),
at: Date.now()
}
try {
if (typeof BroadcastChannel !== 'undefined') {
const bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
bc.postMessage(payload)
bc.close()
}
} catch {
// ignore
}
try {
localStorage.setItem(PROJECT_DELETED_STORAGE_KEY, JSON.stringify(payload))
} catch {
// ignore
}
}
export const listenProjectDeleted = (onDeleted: (projectId: string) => void) => {
const localSessionId = getOrCreateSessionId()
let bc: BroadcastChannel | null = null
const handlePayload = (payload: ProjectDeletedPayload | null) => {
if (!payload) return
if (payload.sourceSessionId === localSessionId) return
onDeleted(payload.projectId)
}
const onStorage = (event: StorageEvent) => {
if (event.key !== PROJECT_DELETED_STORAGE_KEY) return
handlePayload(parseProjectDeletedPayload(event.newValue))
}
if (typeof BroadcastChannel !== 'undefined') {
bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
bc.onmessage = (event: MessageEvent<ProjectDeletedPayload>) => {
handlePayload(parseProjectDeletedPayload(JSON.stringify(event.data)))
}
}
window.addEventListener('storage', onStorage)
return () => {
window.removeEventListener('storage', onStorage)
if (bc) {
bc.close()
bc = null
}
}
}
export const emitResetAll = () => {
const payload: ResetAllPayload = {
type: 'reset-all',
sourceSessionId: getOrCreateSessionId(),
at: Date.now()
}
try {
if (typeof BroadcastChannel !== 'undefined') {
const bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
bc.postMessage(payload)
bc.close()
}
} catch {
// ignore
}
try {
localStorage.setItem(RESET_ALL_STORAGE_KEY, JSON.stringify(payload))
} catch {
// ignore
}
}
export const listenResetAll = (onResetAll: () => void) => {
const localSessionId = getOrCreateSessionId()
let bc: BroadcastChannel | null = null
const handlePayload = (payload: ResetAllPayload | null) => {
if (!payload) return
if (payload.sourceSessionId === localSessionId) return
onResetAll()
}
const onStorage = (event: StorageEvent) => {
if (event.key !== RESET_ALL_STORAGE_KEY) return
handlePayload(parseResetAllPayload(event.newValue))
}
if (typeof BroadcastChannel !== 'undefined') {
bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
bc.onmessage = (event: MessageEvent<ResetAllPayload>) => {
handlePayload(parseResetAllPayload(JSON.stringify(event.data)))
}
}
window.addEventListener('storage', onStorage)
return () => {
window.removeEventListener('storage', onStorage)
if (bc) {
bc.close()
bc = null
}
}
}