podcast manager

fixed custom event bubbling

+32 -19
+5 -1
src/client/page-app.tsx
··· 17 const connectionFallback = (p: RealmConnectionFallbackProps) => 18 p.error ? <h1>Connection Error! {p.error.message}</h1> : <h1>Connection Loading</h1> 19 20 return ( 21 <DatabaseProvider> 22 <RealmIdentityProvider fallback={identityFallback}> 23 - <RealmConnectionProvider fallback={connectionFallback} url="ws://localhost:3001/stream"> 24 <SkypodProvider> 25 <RealmConnectionManager /> 26 <PeerList />
··· 17 const connectionFallback = (p: RealmConnectionFallbackProps) => 18 p.error ? <h1>Connection Error! {p.error.message}</h1> : <h1>Connection Loading</h1> 19 20 + const wshost = window.location.host 21 + const wsproto = window.location.protocol === 'https' ? 'wss' : 'ws' 22 + const wsurl = `${wsproto}://${wshost}/stream` 23 + 24 return ( 25 <DatabaseProvider> 26 <RealmIdentityProvider fallback={identityFallback}> 27 + <RealmConnectionProvider fallback={connectionFallback} url={wsurl}> 28 <SkypodProvider> 29 <RealmConnectionManager /> 30 <PeerList />
+4 -1
src/client/realm/service-connection.ts
··· 92 } 93 94 send<T extends unknown>(identid: IdentID, data: T) { 95 this.sendRaw(identid, JSON.stringify(data)) 96 } 97 ··· 103 } 104 105 broadcast(data: unknown, self = false) { 106 this.broadcastRaw(JSON.stringify(data), self) 107 } 108 ··· 128 } 129 130 #dispatchCustomEvent(type: string, detail?: object) { 131 - this.dispatchEvent(new CustomEvent(type, {detail})) 132 } 133 134 // typed helpers ··· 224 for (const peerid of resp.dat.peers) { 225 if (peerid === this.#identity.identid) continue 226 227 this.#connectPeer(peerid, true) 228 } 229
··· 92 } 93 94 send<T extends unknown>(identid: IdentID, data: T) { 95 + console.debug('sending:', identid, data) 96 this.sendRaw(identid, JSON.stringify(data)) 97 } 98 ··· 104 } 105 106 broadcast(data: unknown, self = false) { 107 + console.debug('broadcasting:', self, data) 108 this.broadcastRaw(JSON.stringify(data), self) 109 } 110 ··· 130 } 131 132 #dispatchCustomEvent(type: string, detail?: object) { 133 + this.dispatchEvent(new CustomEvent(type, {bubbles: true, detail})) 134 } 135 136 // typed helpers ··· 226 for (const peerid of resp.dat.peers) { 227 if (peerid === this.#identity.identid) continue 228 229 + console.debug('connecting...:', peerid) 230 this.#connectPeer(peerid, true) 231 } 232
+20 -16
src/client/skypod/context.tsx
··· 1 - import {useSignalEffect} from '@preact/signals' 2 import {createContext} from 'preact' 3 import {useContext, useEffect, useRef} from 'preact/hooks' 4 import {z} from 'zod/v4' ··· 81 }, 82 ]) 83 84 - const context: SkypodContext = { 85 dispatch: async <K extends keyof ActionMap>(action: ActionMap[K]) => { 86 const actions = [action] as Action[] 87 88 for (const action of actions) { 89 for (const mware of middleware.current) { ··· 98 actions.push(...result) 99 } 100 } 101 - } 102 - 103 - // no-op if not connected 104 - if (!action.opt?.local) { 105 - realm.value?.broadcast(action, false) 106 } 107 } 108 }, ··· 151 middleware.current = [...prefix, ...suffix] 152 } 153 }, 154 - } 155 156 // watch the connection 157 // while we're connected, watch peers for action messages 158 - useSignalEffect(() => { 159 const connection = realm.value 160 if (!connection?.connected) return 161 ··· 165 const parsed = schema.safeParse(event.detail.data) 166 if (parsed.success) { 167 console.log('handling forwarded event:', parsed) 168 - context.dispatch({...parsed.data, opt: {local: true}}).catch((ex: unknown) => { 169 console.error('couldnt dispatch realm action!', ex) 170 }) 171 } ··· 175 return () => { 176 connection.removeEventListener('peerdata', handler as EventListener) 177 } 178 - }) 179 180 const patchSchema = z.union([ 181 z.object({ ··· 199 console.log('message from fetch worker', parsed) 200 201 switch (parsed.data?.msg) { 202 - case 'patch': 203 - await context.dispatch( 204 - context.action('feed:patch', {url: parsed.data.key, payload: parsed.data.changes}), 205 - ) 206 break 207 208 case 'error': 209 default: ··· 224 }) 225 226 console.log('rendering the skypod context') 227 - return <SkypodContext.Provider value={context}>{props.children}</SkypodContext.Provider> 228 } 229 230 export function useSkypod() {
··· 1 import {createContext} from 'preact' 2 import {useContext, useEffect, useRef} from 'preact/hooks' 3 import {z} from 'zod/v4' ··· 80 }, 81 ]) 82 83 + const context = useRef<SkypodContext>({ 84 dispatch: async <K extends keyof ActionMap>(action: ActionMap[K]) => { 85 const actions = [action] as Action[] 86 + 87 + // broadcast if not a local action 88 + if (!action.opt?.local) { 89 + realm.value?.broadcast(action, false) 90 + // TODO: queue these for rebroadcast if not connected 91 + } 92 93 for (const action of actions) { 94 for (const mware of middleware.current) { ··· 103 actions.push(...result) 104 } 105 } 106 } 107 } 108 }, ··· 151 middleware.current = [...prefix, ...suffix] 152 } 153 }, 154 + }) 155 156 // watch the connection 157 // while we're connected, watch peers for action messages 158 + useEffect(() => { 159 const connection = realm.value 160 if (!connection?.connected) return 161 ··· 165 const parsed = schema.safeParse(event.detail.data) 166 if (parsed.success) { 167 console.log('handling forwarded event:', parsed) 168 + context.current.dispatch({...parsed.data, opt: {local: true}}).catch((ex: unknown) => { 169 console.error('couldnt dispatch realm action!', ex) 170 }) 171 } ··· 175 return () => { 176 connection.removeEventListener('peerdata', handler as EventListener) 177 } 178 + }, [context, realm.value]) 179 180 const patchSchema = z.union([ 181 z.object({ ··· 199 console.log('message from fetch worker', parsed) 200 201 switch (parsed.data?.msg) { 202 + case 'patch': { 203 + const action = context.current.action('feed:patch', { 204 + url: parsed.data.key, 205 + payload: parsed.data.changes, 206 + }) 207 + console.log('sending action:', action) 208 + await context.current.dispatch(action) 209 break 210 + } 211 212 case 'error': 213 default: ··· 228 }) 229 230 console.log('rendering the skypod context') 231 + return <SkypodContext.Provider value={context.current}>{props.children}</SkypodContext.Provider> 232 } 233 234 export function useSkypod() {
+2
src/client/skypod/feed-fetch.worker.ts
··· 74 key: feed.url, 75 changes: { 76 ...parsed, 77 lastRefresh: { 78 hp: 100, 79 at: lock, ··· 91 msg: 'patch', 92 key: feed.url, 93 changes: { 94 lastRefresh: { 95 hp: feed.lastRefresh.hp - 10, 96 at: lock,
··· 74 key: feed.url, 75 changes: { 76 ...parsed, 77 + lock: null, 78 lastRefresh: { 79 hp: 100, 80 at: lock, ··· 92 msg: 'patch', 93 key: feed.url, 94 changes: { 95 + lock: null, 96 lastRefresh: { 97 hp: feed.lastRefresh.hp - 10, 98 at: lock,
+1 -1
src/cmd/server.ts
··· 11 const args = parseArgs({ 12 args: process.argv.slice(2), 13 options: { 14 - port: {type: 'string', default: '3001'}, 15 host: {type: 'string', default: '127.0.0.1'}, 16 }, 17 })
··· 11 const args = parseArgs({ 12 args: process.argv.slice(2), 13 options: { 14 + port: {type: 'string', default: '4001'}, 15 host: {type: 'string', default: '127.0.0.1'}, 16 }, 17 })