char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
44fe2c6
main
1import { 2FinalizationRegistry , 3ObjectCreate , 4ReflectApply as apply , 5ReflectConstruct as construct , 6Set , 7SetPrototypeAdd as retain , 8SetPrototypeDelete as release , 9uncurryThis , 10WeakMap , 11WeakMapPrototypeSet as set , 12} from "ext:core/primordials" ; 13import { 14op_broadcast_close , 15op_broadcast_open , 16op_broadcast_send , 17} from "ext:core/ops" ; 18import { array , required , state , string } from "./idl.js" ; 19import { 20addListener , 21createException , 22dataCloneError , 23dispatch , 24type Event , 25EventTarget , 26hasListener , 27observeListeners , 28preventDefault , 29removeListener , 30} from "./events.js" ; 31import { 32adoptPort , 33dispatchMessage , 34messageData , 35MessageEvent , 36MessagePort , 37startPort , 38} from "./messaging.js" ; 39import { 40classify , 41registerUncloneable , 42transferCallbacks , 43} from "./serialization.js" ; 44 45interface BroadcastState { 46__proto__ ?:null ; 47name :string ; 48id :number ; 49port :MessagePort ; 50closed :boolean ; 51onmessage ?:(( event :Event ) => unknown ) | null ; 52onmessageerror ?:(( event :Event ) => unknown ) | null ; 53onmessageListener ?:(( event :Event ) => void ) | null ; 54onmessageerrorListener ?:(( event :Event ) => void ) | null ; 55} 56const channels = new WeakMap < object , BroadcastState >(); 57registerUncloneable ( channels ); 58const closePort = MessagePort . prototype . close ; 59const WeakRef = globalThis . WeakRef , 60deref = uncurryThis ( WeakRef . prototype . deref ); 61const register = uncurryThis ( FinalizationRegistry . prototype . register ), 62unregister = uncurryThis ( FinalizationRegistry . prototype . unregister ); 63const activeChannels = new Set < object >(); 64const finalizer = new FinalizationRegistry <{ id :number ; port :MessagePort }>( 65({ id, port}) => { 66apply ( closePort , port , []); 67op_broadcast_close ( id ); 68}, 69); 70function retainChannel ( target :object ) :void { 71if ( 72! state ( channels , target ). closed && 73( hasListener ( target , "message" ) || hasListener ( target , "messageerror" )) 74) { 75retain ( activeChannels , target ); 76} else release ( activeChannels , target ); 77} 78 79function handler ( 80target :BroadcastChannel , 81current :BroadcastState , 82type :"message" | "messageerror" , 83value :unknown , 84) :void { 85const name = "on" + type as "onmessage" | "onmessageerror" , 86key = name + "Listener" as "onmessageListener" | "onmessageerrorListener" ; 87value = typeof value === "function" ?value :null ; 88current [ name ] = value as (( event :Event ) => unknown ) | null ; 89if ( ! value && current [ key ]) { 90removeListener ( target , type , current [ key ]); 91current [ key ] = null ; 92} else if ( value && ! current [ key ]) { 93current [ key ] = ( event ) => { 94if ( current [ name ] && apply ( current [ name ] ! , target , [ event ]) === false ) { 95preventDefault ( event ); 96} 97}; 98addListener ( target , type , current [ key ]); 99} 100} 101 102class BroadcastChannel extends EventTarget { 103// @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super(). 104constructor ( name :unknown ) { 105required ( arguments . length , 1 ); 106name = string ( name ); 107const self = construct ( EventTarget , [], new . target ) as BroadcastChannel ; 108const id = op_broadcast_open ( name as string ), port = adoptPort ( id ); 109const current :BroadcastState = { 110__proto__ :null , 111name :name as string , 112 id, 113 port, 114closed :false , 115}; 116set ( channels , self , current ); 117observeListeners ( self , retainChannel ); 118const weak = new WeakRef ( self ); 119addListener ( port , "message" , ( event :Event ) => { 120const target = deref ( weak ); 121if ( target && ! state ( channels , target ). closed ) { 122dispatchMessage ( target , messageData ( event )); 123} 124}); 125addListener ( port , "messageerror" , () => { 126const target = deref ( weak ); 127if ( target && ! state ( channels , target ). closed ) { 128dispatch ( target , new MessageEvent ( "messageerror" , ObjectCreate ( null ))); 129} 130}); 131register ( finalizer , self , { id, port}, self ); 132startPort ( port ); 133return self as BroadcastChannel ; 134} 135get name () { 136return state ( channels , this ). name ; 137} 138postMessage ( message :unknown ) :void { 139const current = state ( channels , this ); 140required ( arguments . length , 1 ); 141if ( current . closed ) { 142throw createException ({ 143name :"InvalidStateError" , 144message :"BroadcastChannel is closed" , 145}); 146} 147const callbacks = transferCallbacks ( array (), dataCloneError , true ); 148try { 149op_broadcast_send ( 150current . id , 151{ __proto__ :null , data :message , ports :[] }, 152classify , 153callbacks . encodeHost , 154dataCloneError , 155callbacks . validate , 156callbacks . commit , 157); 158} finally { 159callbacks . clear (); 160} 161} 162close () { 163const current = state ( channels , this ); 164if ( current . closed ) return ; 165current . closed = true ; 166retainChannel ( this ); 167unregister ( finalizer , this ); 168apply ( closePort , current . port , []); 169op_broadcast_close ( current . id ); 170} 171get onmessage () { 172return state ( channels , this ). onmessage ?? null ; 173} 174set onmessage ( value ) { 175handler ( this , state ( channels , this ), "message" , value ); 176} 177get onmessageerror () { 178return state ( channels , this ). onmessageerror ?? null ; 179} 180set onmessageerror ( value ) { 181handler ( this , state ( channels , this ), "messageerror" , value ); 182} 183} 184 185export { BroadcastChannel };