You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
i get push notifications when i have issues or PRs open in my browser = i see live edits and updates without page reload. maybe we can abuse that? but then, will that scale to a million subscriptions? a billion subscriptions? i guess github will not like this
importtype{AliveEvent,MetadataUpdate,Notifier,Subscription}from'@github/alive-client'import{PresenceMetadataSet,SubscriptionSet,isPresenceChannel}from'@github/alive-client'import{AliveSession}from'./session'import{debounce}from'@github/mini-throttle'import{ready}from'@github-ui/document-ready'importsafeStoragefrom'@github-ui/safe-storage'import{workerSrcRelPolicy,SourceRelNotFoundError}from'@github-ui/trusted-types-policies/worker-src-rel'exportinterfaceDispatchable{
dispatchEvent: (e: Event)=>unknown}functionisSharedWorkerSupported(): boolean{return'SharedWorker'inwindow&&safeStorage('localStorage').getItem('bypassSharedWorker')!=='true'}functionworkerSrc(): string|null{try{returnworkerSrcRelPolicy.createScriptURL('shared-web-socket-src')}catch(e){if(einstanceofSourceRelNotFoundError){returnnull}throwe}}functionsocketUrl(): string|null{returndocument.head.querySelector<HTMLLinkElement>('link[rel=shared-web-socket]')?.href??null}functionsocketRefreshUrl(): string|null{return(document.head.querySelector<HTMLLinkElement>('link[rel=shared-web-socket]')?.getAttribute('data-refresh-url')??null)}functionsessionIdentifier(): string|null{return(document.head.querySelector<HTMLLinkElement>('link[rel=shared-web-socket]')?.getAttribute('data-session-id')??null)}functionnotify(subscribers: Iterable<Dispatchable>,{channel, type, data}: AliveEvent){for(constelofsubscribers){el.dispatchEvent(newCustomEvent(`socket:${type}`,{bubbles: false,cancelable: false,detail: {name: channel, data},}),)}}classAliveSessionProxy{privateworker: SharedWorkerprivatesubscriptions=newSubscriptionSet<Dispatchable>()privatepresenceMetadata=newPresenceMetadataSet<Dispatchable>()privatenotify: Notifier<Dispatchable>constructor(src: string,url: string,refreshUrl: string,sessionId: string,notifier: Notifier<Dispatchable>){
this.notify=notifier// eslint-disable-next-line ssr-friendly/no-dom-globals-in-constructorthis.worker=newSharedWorker(src,`github-socket-worker-v2-${sessionId}`)this.worker.port.onmessage=({data})=>this.receive(data)this.worker.port.postMessage({connect: {url, refreshUrl}})}subscribe(subs: Array<Subscription<Dispatchable>>){const added =this.subscriptions.add(...subs)if(added.length){this.worker.port.postMessage({subscribe: added})}// We may be adding a subscription to a presence channel which is already subscribed.// In this case, we need to explicitly ask the SharedWorker to send us the presence data.constaddedChannels=newSet(added.map(topic=>topic.name))constredundantPresenceChannels=subs.reduce((redundantChannels,subscription)=>{constchannel=subscription.topic.nameif(isPresenceChannel(channel)&&!addedChannels.has(channel)){redundantChannels.add(channel)}returnredundantChannels},newSet<string>())if(redundantPresenceChannels.size){this.worker.port.postMessage({requestPresence: Array.from(redundantPresenceChannels)})}}unsubscribeAll(...subscribers: Dispatchable[]){constremoved=this.subscriptions.drain(...subscribers)if(removed.length){this.worker.port.postMessage({unsubscribe: removed})}constupdatedPresenceChannels=this.presenceMetadata.removeSubscribers(subscribers)this.sendPresenceMetadataUpdate(updatedPresenceChannels)}updatePresenceMetadata(metadataUpdates: Array<MetadataUpdate<Dispatchable>>){const updatedChannels =newSet<string>()for(constupdateofmetadataUpdates){// update the local metadata for this specific element
this.presenceMetadata.setMetadata(update)updatedChannels.add(update.channelName)}// Send the full local metadata for these channels to the SharedWorkerthis.sendPresenceMetadataUpdate(updatedChannels)}sendPresenceMetadataUpdate(channelNames: Set<string>){if(!channelNames.size){return}constupdatesForSharedWorker: Array<Omit<MetadataUpdate<Element>,'subscriber'>>=[]for(constchannelNameofchannelNames){// get all metadata for this channel (from all elements) to send to the SharedWorker
updatesForSharedWorker.push({
channelName,metadata: this.presenceMetadata.getChannelMetadata(channelName),})}// Send the full metadata updates to the SharedWorkerthis.worker.port.postMessage({updatePresenceMetadata: updatesForSharedWorker})}online(){this.worker.port.postMessage({online: true})}offline(){this.worker.port.postMessage({online: false})}hangup(){this.worker.port.postMessage({hangup: true})}privatenotifyPresenceDebouncedByChannel=newMap<string,Notifier<Dispatchable>>()privatereceive(event: AliveEvent){const{channel}=eventif(event.type==='presence'){// There are times when we get a flood of messages from the SharedWorker, such as a tab that has been idle for a long time and then comes back to the foreground.// Since each presence message for a channel contains the full list of users, we can debounce the events and only notify subscribers with the last onelet debouncedNotify =this.notifyPresenceDebouncedByChannel.get(channel)if(!debouncedNotify){
debouncedNotify =debounce((subscribers,debouncedEvent)=>{this.notify(subscribers,debouncedEvent)this.notifyPresenceDebouncedByChannel.delete(channel)},100)this.notifyPresenceDebouncedByChannel.set(channel,debouncedNotify)}debouncedNotify(this.subscriptions.subscribers(channel),event)return}// For non-presence messages, we can send them through immediately since they may contain different messages/datathis.notify(this.subscriptions.subscribers(channel),event)}}asyncfunctionconnect(){constsrc=workerSrc()if(!src)returnconsturl=socketUrl()if(!url)returnconstrefreshUrl=socketRefreshUrl()if(!refreshUrl)returnconstsessionId=sessionIdentifier()if(!sessionId)returnconstcreateSession=()=>{if(isSharedWorkerSupported()){try{returnnewAliveSessionProxy(src,url,refreshUrl,sessionId,notify)}catch(_){// ignore errors. CSP will some times block SharedWorker creation. Fall back to standard AliveSession.}}returnnewAliveSession(url,refreshUrl,false,notify)}constsession=createSession()window.addEventListener('online',()=>session.online())window.addEventListener('offline',()=>session.offline())window.addEventListener('pagehide',()=>{if('hangup'insession)session.hangup()})returnsession}asyncfunctionconnectWhenReady(){awaitreadyreturnconnect()}letsessionPromise: undefined|ReturnType<typeofconnectWhenReady>exportfunctiongetSession(){return(sessionPromise||=connectWhenReady())}
import{hasInteractions}from'./has-interactions'// eslint-disable-next-line no-restricted-importsimport{observe}from'@github/selector-observer'import{parseHTML}from'./parse-html'import{preserveAnchorNodePosition}from'scroll-anchoring'import{replaceState}from'./history'constpendingRequests=newWeakMap<HTMLElement,AbortController>()conststaleRecords: {[key: string]: string}={}// Wrapper around `window.location.reload()` that forceably cleans out the// `staleRecords` state associated with the entry at the top of the history// stack before reloading.exportfunctionreload(){for(constkeyofObject.keys(staleRecords)){deletestaleRecords[key]}conststateObject=history.state||{}stateObject.staleRecords=staleRecordsreplaceState(stateObject,'',location.href)window.location.reload()}// Associates the `staleRecords` object, if it contains any entries, with the// entry at top of the history stack.exportfunctionregisterStaleRecords(){if(Object.keys(staleRecords).length>0){conststateObject=history.state||{}stateObject.staleRecords=staleRecordsreplaceState(stateObject,'',location.href)}}// Fetch and replace container with its data-url.//// This replacement uses conservative checks to safely replace the element.// If a user is interacting with any element within the container, the// replacement will be aborted.exportasyncfunctionupdateContent(el: HTMLElement): Promise<void>{if(pendingRequests.get(el))returnconstretainFocus=el.hasAttribute('data-retain-focus')consturl=el.getAttribute('data-url')if(!url)thrownewError('could not get url')constcontroller=newAbortController()pendingRequests.set(el,controller)try{constresponse=awaitfetch(url,{signal: controller.signal,headers: {Accept: 'text/html','X-Requested-With': 'XMLHttpRequest',},})if(!response.ok)returnconstdata=awaitresponse.text()if(hasInteractions(el,retainFocus)){// eslint-disable-next-line no-consoleconsole.warn('Failed to update content with interactions',el)return}staleRecords[url]=datareturnreplace(el,data,retainFocus)}catch{// Ignore failed request.}finally{pendingRequests.delete(el)}}// Abort any in-flight replacements and replace element without any interaction checks.exportasyncfunctionreplaceContent(el: HTMLElement,data: string,wasStale=false): Promise<void>{constcontroller=pendingRequests.get(el)controller?.abort()constupdatable=el.closest('.js-updatable-content[data-url], .js-updatable-content [data-url]')if(!wasStale&&updatable&&updatable===el){staleRecords[updatable.getAttribute('data-url')||'']=data}returnreplace(el,data)}functionreplace(el: HTMLElement,data: string,retainFocus=false): Promise<void>{returnpreserveAnchorNodePosition(document,()=>{constnewContent=parseHTML(document,data.trim())constelementToRefocus=retainFocus&&el.ownerDocument&&el===el.ownerDocument.activeElement ? newContent.querySelector('*') : nullconstdetailsIds=Array.from(el.querySelectorAll('details[open][id]')).map(element=>element.id)if(el.tagName==='DETAILS'&&el.id&&el.hasAttribute('open'))detailsIds.push(el.id)// Check the elements we are about replace to see if we want to preserve the scroll position of any of themfor(constpreserveElementofel.querySelectorAll('.js-updatable-content-preserve-scroll-position')){constid=preserveElement.getAttribute('data-updatable-content-scroll-position-id')||''heights.set(id,preserveElement.scrollTop)}for(constidofdetailsIds){constdetails=newContent.querySelector(`#${id}`)if(details)details.setAttribute('open','')}el.replaceWith(newContent)if(elementToRefocusinstanceofHTMLElement){elementToRefocus.focus()}})}constheights=newMap()observe('.js-updatable-content-preserve-scroll-position',{constructor: HTMLElement,add(el){// When element is added to the DOM, check the map for the last scroll position we have on record for it.constid=el.getAttribute('data-updatable-content-scroll-position-id')if(!id)returnconstheight=heights.get(id)if(height==null)returnel.scrollTop=height},})
keywords: github push events per issue, service worker, alive client, alive.ts, websocket, pubsub, presence, liveupdate, live update, liveview, live view, server sent events, updatable-content.ts, updatable-content-observer.ts
The text was updated successfully, but these errors were encountered:
#1
push event callstack
https://github.githubassets.com/assets/ui/packages/alive/alive.ts
https://github.githubassets.com/assets/app/assets/modules/github/behaviors/updatable-content-observer.ts
https://github.githubassets.com/assets/app/assets/modules/github/updatable-content.ts
keywords: github push events per issue, service worker, alive client, alive.ts, websocket, pubsub, presence, liveupdate, live update, liveview, live view, server sent events, updatable-content.ts, updatable-content-observer.ts
The text was updated successfully, but these errors were encountered: