@3dsource/angular-unreal-module 0.0.146-dev.8 → 0.0.146-dev.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{"use strict";const f="metabox-configurator",m=/metabox-configurator\/(modular|basic)\/([^/?#]+)/i,g="3dsource:region-ping",k="assets/config.json",w="42e01f";function l(){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{const t=Math.random()*16|0;return(o==="x"?t:t&3|8).toString(16)})}function I(o){return o.replace("http://","ws://").replace("https://","wss://")}const h="[stream-prefetch]",s=(...o)=>console.log(h,...o),c=(...o)=>console.warn(h,...o),u=(...o)=>console.error(h,...o);class y{constructor(){const t=document.currentScript||(()=>{const n=document.getElementsByTagName("script");return n[n.length-1]})(),e=t&&t.dataset||{};this.configUrl=e.configUrl||k,this.fallbackPollingUrl=e.fallbackUrl||"",this.skipRouteCheck=this.boolOr(e.skipRouteCheck,!1),this.config={ws_timeout:this.numberOr(e.wsTimeout,6e3),poll_timeout:this.numberOr(e.pollTimeout,4e3)};const i=new URLSearchParams(window.location.search);this.forceTURN=i.has("ForceTURN"),this.forceMaxBundle=i.has("ForceMaxBundle"),this.forceMonoAudio=i.has("forceMono"),this.ids={clientId:l(),viewId:l(),correlationId:l()},this.context=null,this.environmentId=void 0,this.orchestrationType="",this.region="",this.requireEnvironmentId=!0,this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.wsTimeoutId=null,this.settled=!1}run(){if(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window))return;if(this.skipRouteCheck){this.context={},this.requireEnvironmentId=!1,c("skipRouteCheck=true \u2014 ignoring URL gate, route, context and environmentId; using fallback polling"),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e));return}if(window.location.href.indexOf(f)===-1){s("skip \u2014 URL does not contain gate",`"${f}"`);return}const t=this.parseConfiguratorUrl();t?(this.context={id:t.contextId,data:{configuratorId:t.configuratorId}},this.requireEnvironmentId=!0,s("start",{contextId:t.contextId,configuratorId:t.configuratorId,ids:this.ids})):(this.context={},this.requireEnvironmentId=!1,c("URL is not a configurator route \u2014 prefetching with empty context {} and no required environmentId",window.location.pathname)),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e))}parseConfiguratorUrl(){const t=window.location.pathname.match(m);return t?{contextId:t[1].toLowerCase()==="modular"?"metaboxModularConfigurator":"metaboxBasicConfigurator",configuratorId:decodeURIComponent(t[2])}:null}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",s("region from cache:",this.region||"(none)"),(this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(e=>{let i;if(e)i=this.chooseEndpoint(e);else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),i={type:"polling",url:this.fallbackPollingUrl,environmentId:""},this.requireEnvironmentId=!1;else{this.fail("no-config");return}if(!i){this.fail("no-endpoint");return}if(s("endpoint chosen",{type:i.type,url:i.url,environmentId:i.environmentId}),this.requireEnvironmentId&&!i.environmentId){this.fail("no-environment-id");return}this.orchestrationType=i.type,this.environmentId=i.environmentId||void 0;const n=this.adaptUrlToRegion(i.url);return(i.type==="polling"?this.pollOrchestration(n):Promise.resolve(n)).then(a=>{if(!a){this.fail("no-ws-url");return}return this.parked.machineId=a,s("opening socket",a),this.openSocket(a)})})}fetchConfig(){const t=new URL(this.configUrl,window.location.origin).href;return this.fetchWithTimeout(t,{credentials:"omit"},this.config.poll_timeout).then(e=>e.ok?e.json().catch(()=>null):null).catch(()=>null)}chooseEndpoint(t){const e=t.newSignalling,i=this.firstVersionUrl(e&&e.versions);if(i&&/^wss?:\/\//i.test(i))return{type:"direct",url:i,environmentId:e&&e.environmentId||""};const n=t.signalling,r=this.firstVersionUrl(n&&n.versions);return r?(this.requireEnvironmentId=!1,{type:"polling",url:r,environmentId:n&&n.environmentId||""}):null}firstVersionUrl(t){if(!t||typeof t!="object")return"";for(const e of Object.keys(t))if(t[e])return t[e];return""}pollOrchestration(t){const e=`${t}${this.ids.clientId}/${this.ids.viewId}`;return this.fetchWithTimeout(e,{credentials:"omit"},this.config.poll_timeout).then(i=>i.ok?i.json().catch(()=>({})):null).then(i=>i&&i.signallingServer?I(`${window.location.protocol}//${i.signallingServer}`):null).catch(()=>null)}adaptUrlToRegion(t){return this.region&&t.indexOf(":region")===-1?t.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):t}openSocket(t){const e=new WebSocket(t);e.binaryType="arraybuffer",this.ws=e,this.parked.ws=e,this.wsTimeoutId=setTimeout(()=>{e.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),e.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),s("WS open \u2192 sending requestStream",{environmentId:this.environmentId,region:this.region,context:this.context}),this.sendRequestStream()},e.onmessage=i=>this.onWsMessage(i),e.onerror=i=>{u("WS error",i),this.fail("ws-error")},e.onclose=i=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:i&&i.code}),this.fail("ws-closed"))}}sendRequestStream(){this.send({type:"requestStream",correlationId:this.ids.correlationId,payload:{region:this.region,environmentId:this.environmentId,context:this.context}})}onWsMessage(t){let e;try{const i=typeof t.data=="string"?t.data:null;if(i===null)return;e=JSON.parse(i)}catch{return}switch(s("WS \u2190",e.type),e.type){case"ssInfo":this.parked.ssData=e.data;break;case"config":this.onConfig(e);break;case"offer":this.onOffer(e);break;case"answer":this.onAnswer(e);break;case"iceCandidate":this.onIceCandidate(e.candidate);break;case"ping":this.send({type:"pong",source:"front"});break;case"streamerDisconnected":case"playerDisconnected":this.fail("streamer-disconnected");break;case"error":this.fail("orchestration-error");break;default:break}}onConfig(t){if(this.pc)return;const e=Object.assign({},t.peerConnectionOptions);this.forceTURN&&(e.iceTransportPolicy="relay"),e.bundlePolicy=this.forceMaxBundle?"max-bundle":"balanced",e.iceCandidatePoolSize=1,this.parked.config=e;const i=new RTCPeerConnection(e);this.pc=i,this.parked.pc=i,s("PeerConnection created from config"),i.onicecandidate=n=>{n.candidate&&this.send({type:"iceCandidate",candidate:n.candidate})},i.ondatachannel=n=>this.wireDataChannel(n.channel),i.ontrack=n=>{n.track&&n.track.kind==="video"&&n.streams[0]&&(s("video track received"),this.stream=n.streams[0],this.parked.stream=this.stream,this.maybeReady())}}onOffer(t){return this.pc?(this.remoteDescriptionSet=!1,this.iceBuffer.length=0,this.pc.setRemoteDescription(t).then(()=>(this.remoteDescriptionSet=!0,this.flushIce(),this.pc.createAnswer())).then(e=>(e.sdp=this.mungeSDP(e.sdp||""),this.pc.setLocalDescription(e).then(()=>{s("WS \u2192 answer sent"),this.send(e)}))).catch(e=>this.fail("offer-failed",e))):Promise.resolve()}onAnswer(t){return this.pc?this.pc.setRemoteDescription(t).then(()=>{this.remoteDescriptionSet=!0,this.flushIce()}).catch(e=>this.fail("answer-failed",e)):Promise.resolve()}onIceCandidate(t){t&&(this.forceTURN&&String(t.candidate).indexOf("relay")<0||(this.remoteDescriptionSet&&this.pc?this.pc.addIceCandidate(t).catch(()=>{}):this.iceBuffer.push(t)))}flushIce(){for(const t of this.iceBuffer)this.pc.addIceCandidate(t).catch(()=>{});this.iceBuffer.length=0}wireDataChannel(t){t.binaryType="arraybuffer",this.dc=t,this.parked.dc=t,t.onmessage=e=>{this.parked.dcBuffer.push(e.data)},t.onopen=()=>{s("DataChannel open"),this.mark("dc_open"),this.maybeReady()},t.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&(c("DataChannel closed before ready"),this.fail("dc-closed"))},t.readyState==="open"&&this.maybeReady()}maybeReady(){this.settled||!this.dc||this.dc.readyState!=="open"||this.stream&&(this.settled=!0,this.mark("ready"),this.parked.state="ready",s("READY \u2014 parked on window.__stream",this.parked.timings))}publishParked(t){const e=this;this.parked={state:t,pc:null,dc:null,stream:null,ws:null,ssData:void 0,machineId:void 0,config:void 0,ids:this.ids,dcBuffer:[],timings:{},adopt(){return e.clearTimers(),this.state="adopted",this}},window.__stream=this.parked}clearTimers(){this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null)}fail(t,e){if(!(this.parked&&(this.parked.state==="adopted"||this.parked.state==="failed"))){u("FAILED:",t,e||""),this.settled=!0,this.clearTimers();try{this.dc&&this.dc.close()}catch{}try{this.pc&&this.pc.close()}catch{}try{this.ws&&this.ws.close()}catch{}this.parked&&(this.parked.state="failed",this.parked.failReason=t,e&&(this.parked.failError=String(e&&e.message||e)))}}mungeSDP(t){let e=this.prioritizeH264Profile(t,w);e=e.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,`$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r
1
+ "use strict";(()=>{"use strict";const f="metabox-configurator",m=/metabox-configurator\/(modular|basic)\/([^/?#]+)/i,g="3dsource:region-ping",k="assets/config.json",w="42e01f";function l(){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{const t=Math.random()*16|0;return(o==="x"?t:t&3|8).toString(16)})}function I(o){return o.replace("http://","ws://").replace("https://","wss://")}const h="[stream-prefetch]",s=(...o)=>console.log(h,...o),c=(...o)=>console.warn(h,...o),u=(...o)=>console.error(h,...o);class y{constructor(){const t=document.currentScript||(()=>{const n=document.getElementsByTagName("script");return n[n.length-1]})(),e=t&&t.dataset||{};this.configUrl=e.configUrl||k,this.fallbackPollingUrl=e.fallbackUrl||"",this.skipRouteCheck=this.boolOr(e.skipRouteCheck,!1),this.config={ws_timeout:this.numberOr(e.wsTimeout,6e3),poll_timeout:this.numberOr(e.pollTimeout,4e3)};const i=new URLSearchParams(window.location.search);this.forceTURN=i.has("ForceTURN"),this.forceMaxBundle=i.has("ForceMaxBundle"),this.forceMonoAudio=i.has("forceMono"),this.ids={clientId:l(),viewId:l(),correlationId:l()},this.context=null,this.environmentId=void 0,this.orchestrationType="",this.region="",this.requireEnvironmentId=!0,this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.wsTimeoutId=null,this.settled=!1}run(){if(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window))return;if(this.skipRouteCheck){this.context={},this.requireEnvironmentId=!1,c("skipRouteCheck=true \u2014 ignoring URL gate, route, context and environmentId; using fallback polling"),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e));return}if(window.location.href.indexOf(f)===-1){s("skip \u2014 URL does not contain gate",`"${f}"`);return}const t=this.parseConfiguratorUrl();t?(this.context={id:t.contextId,data:{configuratorId:t.configuratorId}},this.requireEnvironmentId=!0,s("start",{contextId:t.contextId,configuratorId:t.configuratorId,ids:this.ids})):(this.context={},this.requireEnvironmentId=!1,c("URL is not a configurator route \u2014 prefetching with empty context {} and no required environmentId",window.location.pathname)),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e))}parseConfiguratorUrl(){const t=window.location.pathname.match(m);return t?{contextId:t[1].toLowerCase()==="modular"?"metaboxModularConfigurator":"metaboxBasicConfigurator",configuratorId:decodeURIComponent(t[2])}:null}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",s("region from cache:",this.region||"(none)"),(this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(e=>{let i;if(e)i=this.chooseEndpoint(e);else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),i={type:"polling",url:this.fallbackPollingUrl,environmentId:""},this.requireEnvironmentId=!1;else{this.fail("no-config");return}if(!i){this.fail("no-endpoint");return}if(s("endpoint chosen",{type:i.type,url:i.url,environmentId:i.environmentId}),this.requireEnvironmentId&&!i.environmentId){this.fail("no-environment-id");return}this.orchestrationType=i.type,this.environmentId=i.environmentId||void 0;const n=this.adaptUrlToRegion(i.url);return(i.type==="polling"?this.pollOrchestration(n):Promise.resolve(n)).then(a=>{if(!a){this.fail("no-ws-url");return}return this.parked.machineId=a,s("opening socket",a),this.openSocket(a)})})}fetchConfig(){const t=new URL(this.configUrl,window.location.origin).href;return this.fetchWithTimeout(t,{credentials:"omit"},this.config.poll_timeout).then(e=>e.ok?e.json().catch(()=>null):null).catch(()=>null)}chooseEndpoint(t){const e=t.newSignalling,i=this.firstVersionUrl(e&&e.versions);if(i&&/^wss?:\/\//i.test(i))return{type:"direct",url:i,environmentId:e&&e.environmentId||""};const n=t.signalling,r=this.firstVersionUrl(n&&n.versions);return r?(this.requireEnvironmentId=!1,{type:"polling",url:r,environmentId:n&&n.environmentId||""}):null}firstVersionUrl(t){if(!t||typeof t!="object")return"";for(const e of Object.keys(t))if(t[e])return t[e];return""}pollOrchestration(t){const e=`${t}${this.ids.clientId}/${this.ids.viewId}`;return this.fetchWithTimeout(e,{credentials:"omit"},this.config.poll_timeout).then(i=>i.ok?i.json().catch(()=>({})):null).then(i=>i&&i.signallingServer?I(`${window.location.protocol}//${i.signallingServer}`):null).catch(()=>null)}adaptUrlToRegion(t){return this.region&&t.indexOf(":region")===-1?t.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):t}openSocket(t){const e=new WebSocket(t);e.binaryType="arraybuffer",this.ws=e,this.parked.ws=e,this.wsTimeoutId=setTimeout(()=>{e.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),e.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),s("WS open \u2192 sending requestStream",{environmentId:this.environmentId,region:this.region,context:this.context}),this.sendRequestStream()},e.onmessage=i=>this.onWsMessage(i),e.onerror=i=>{u("WS error",i),this.fail("ws-error")},e.onclose=i=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:i&&i.code}),this.fail("ws-closed"))}}sendRequestStream(){this.send({type:"requestStream",correlationId:this.ids.correlationId,payload:{region:this.region,environmentId:this.environmentId,context:this.context}})}onWsMessage(t){let e;try{const i=typeof t.data=="string"?t.data:null;if(i===null)return;e=JSON.parse(i)}catch{return}switch(s("WS \u2190",e.type),e.type){case"ssInfo":this.parked.ssData=e.data;break;case"config":this.onConfig(e);break;case"offer":this.onOffer(e);break;case"answer":this.onAnswer(e);break;case"iceCandidate":this.onIceCandidate(e.candidate);break;case"ping":this.send({type:"pong",source:"front"});break;case"streamerDisconnected":case"playerDisconnected":this.fail("streamer-disconnected");break;case"error":this.fail("orchestration-error");break;default:break}}onConfig(t){if(this.pc)return;const e=Object.assign({},t.peerConnectionOptions);this.forceTURN&&(e.iceTransportPolicy="relay"),e.bundlePolicy=this.forceMaxBundle?"max-bundle":"balanced",e.iceCandidatePoolSize=1,this.parked.config=e;const i=new RTCPeerConnection(e);this.pc=i,this.parked.pc=i,s("PeerConnection created from config"),i.onicecandidate=n=>{n.candidate&&this.send({type:"iceCandidate",candidate:n.candidate})},i.ondatachannel=n=>this.wireDataChannel(n.channel),i.ontrack=n=>{n.track&&n.track.kind==="video"&&n.streams[0]&&(s("video track received"),this.stream=n.streams[0],this.parked.stream=this.stream,this.maybeReady())}}onOffer(t){return this.pc?(this.remoteDescriptionSet=!1,this.iceBuffer.length=0,this.pc.setRemoteDescription(t).then(()=>(this.remoteDescriptionSet=!0,this.flushIce(),this.pc.createAnswer())).then(e=>(e.sdp=this.mungeSDP(e.sdp||""),this.pc.setLocalDescription(e).then(()=>{s("WS \u2192 answer sent"),this.send(e)}))).catch(e=>this.fail("offer-failed",e))):Promise.resolve()}onAnswer(t){return this.pc?this.pc.setRemoteDescription(t).then(()=>{this.remoteDescriptionSet=!0,this.flushIce()}).catch(e=>this.fail("answer-failed",e)):Promise.resolve()}onIceCandidate(t){t&&(this.forceTURN&&String(t.candidate).indexOf("relay")<0||(this.remoteDescriptionSet&&this.pc?this.pc.addIceCandidate(t).catch(()=>{}):this.iceBuffer.push(t)))}flushIce(){for(const t of this.iceBuffer)this.pc.addIceCandidate(t).catch(()=>{});this.iceBuffer.length=0}wireDataChannel(t){t.binaryType="arraybuffer",this.dc=t,this.parked.dc=t,t.onmessage=e=>{this.parked.dcBuffer.push(e.data)},t.onopen=()=>{s("DataChannel open"),this.mark("dc_open"),this.maybeReady()},t.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&(c("DataChannel closed before ready"),this.fail("dc-closed"))},t.readyState==="open"&&this.maybeReady()}maybeReady(){this.settled||!this.dc||this.dc.readyState!=="open"||this.stream&&(this.settled=!0,this.mark("ready"),this.parked.state="ready",s("READY \u2014 parked on window.__stream",this.parked.timings),this.fireSettled())}publishParked(t){const e=this;this.parked={state:t,pc:null,dc:null,stream:null,ws:null,ssData:void 0,machineId:void 0,config:void 0,ids:this.ids,dcBuffer:[],timings:{},adopt(){return e.clearTimers(),this.state="adopted",this},onSettled(i){if(typeof i=="function"){if(this.state!=="connecting"){const n=this.state;Promise.resolve().then(()=>i(n));return}e.settledCb=i}}},window.__stream=this.parked}fireSettled(){const t=this.settledCb;if(t){this.settledCb=null;try{t(this.parked.state)}catch{}}}clearTimers(){this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null)}fail(t,e){if(!(this.parked&&(this.parked.state==="adopted"||this.parked.state==="failed"))){u("FAILED:",t,e||""),this.settled=!0,this.clearTimers();try{this.dc&&this.dc.close()}catch{}try{this.pc&&this.pc.close()}catch{}try{this.ws&&this.ws.close()}catch{}this.parked&&(this.parked.state="failed",this.parked.failReason=t,e&&(this.parked.failError=String(e&&e.message||e)),this.fireSettled())}}mungeSDP(t){let e=this.prioritizeH264Profile(t,w);e=e.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,`$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r
2
2
  `);let i="maxaveragebitrate=510000;";return i+=this.forceMonoAudio?"stereo=0;":"stereo=1;",i+="useinbandfec=1",e=e.replace("useinbandfec=1",i),e}prioritizeH264Profile(t,e){const i=t.split(`\r
3
3
  `),n=[];for(const r of i)if(r.startsWith("a=fmtp:")&&r.toLowerCase().includes(`profile-level-id=${e}`)){const a=r.split(":")[1]&&r.split(":")[1].split(" ")[0];a&&n.push(a)}return n.length===0?t:i.map(r=>{if(!r.startsWith("m=video"))return r;const a=r.split(" "),x=a.slice(0,3),p=a.slice(3),b=[...n.filter(d=>p.includes(d)),...p.filter(d=>!n.includes(d))];return[...x,...b].join(" ")}).join(`\r
4
4
  `)}send(t){const e=typeof t=="string"?t:JSON.stringify(t);if(this.ws&&this.ws.readyState===WebSocket.OPEN)try{this.ws.send(e)}catch{}}mark(t){this.parked&&(this.parked.timings[t]=performance.now())}readRegion(){try{const t=localStorage.getItem(g);if(!t)return null;const e=JSON.parse(t);return!e||typeof e.region_code!="string"||typeof e.ts!="number"||Date.now()-e.ts>=2592e6?null:e.region_code}catch{return null}}fetchWithTimeout(t,e,i){const n=new AbortController;e.signal=n.signal;const r=setTimeout(()=>n.abort(),i);return fetch(t,e).finally(()=>clearTimeout(r))}numberOr(t,e){const i=Number(t);return t!=null&&t!==""&&!Number.isNaN(i)?i:e}boolOr(t,e){return t==="true"||t===""?!0:t==="false"?!1:e}}new y().run()})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3dsource/angular-unreal-module",
3
- "version": "0.0.146-dev.8",
3
+ "version": "0.0.146-dev.9",
4
4
  "description": "Unreal module for connect with unreal engine stream",
5
5
  "keywords": [
6
6
  "3dsource",
@@ -287,6 +287,14 @@ interface ParkedStream {
287
287
  failError?: string;
288
288
  /** Stops the script's watchdog and flips state to `adopted`. */
289
289
  adopt: () => ParkedStream;
290
+ /**
291
+ * Register a one-shot callback fired the instant the stream settles
292
+ * (`ready`/`failed`); fires on the next microtask if already settled. Lets
293
+ * {@link StreamAdoptionService} adopt event-driven instead of polling.
294
+ * Optional — absent on head-script versions predating the hook, where the
295
+ * service falls back to polling.
296
+ */
297
+ onSettled?: (cb: (state: ParkedStream['state']) => void) => void;
290
298
  }
291
299
  declare global {
292
300
  interface Window {
@@ -1097,7 +1105,9 @@ declare class StreamAdoptionService {
1097
1105
  *
1098
1106
  * - nothing parked → emit `false` at once (caller connects normally).
1099
1107
  * - already settled (`ready`/`failed`/`adopted`) → adopt-if-ready at once.
1100
- * - still `connecting` → poll until the script settles it, then adopt-if-ready.
1108
+ * - still `connecting` → wait for the script's `onSettled` callback (no
1109
+ * polling latency) and adopt-if-ready the instant it fires; on older
1110
+ * scripts without the hook, fall back to a `timer` poll.
1101
1111
  *
1102
1112
  * Deliberately NO time budget: for DIRECT orchestration the prefetch holds
1103
1113
  * the socket open while the orchestrator provisions a machine, which can take
@@ -1118,6 +1128,16 @@ declare class StreamAdoptionService {
1118
1128
  * exist yet and is lost. `defer` guarantees that ordering.
1119
1129
  */
1120
1130
  awaitAndAdopt$(): Observable<boolean>;
1131
+ /**
1132
+ * Event-driven wait: resolve the instant the script's `onSettled` callback
1133
+ * fires (ready/failed), then adopt-if-ready. Emits exactly once.
1134
+ *
1135
+ * A coarse {@link ADOPT_EVENT_SAFETY_MS} interval is a backstop only — if
1136
+ * `onSettled` somehow never fires it still settles, without adding latency to
1137
+ * the normal case. Teardown (route change → unsubscribe) sets `done`, so a
1138
+ * late callback after cancel can't trigger a stray adoption.
1139
+ */
1140
+ private awaitSettled$;
1121
1141
  /**
1122
1142
  * @returns `true` if a ready parked stream was adopted (caller must NOT
1123
1143
  * start a fresh connection), `false` otherwise.