@3dsource/angular-unreal-module 0.0.146-dev.0 → 0.0.146-dev.10

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 l="metabox-configurator",d=/metabox-configurator\/(modular|basic)\/([^/?#]+)/i,u="3dsource:region-ping",f="assets/config.json",p="42e01f";function a(){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{const e=Math.random()*16|0;return(o==="x"?e:e&3|8).toString(16)})}function m(o){return o.replace("http://","ws://").replace("https://","wss://")}class g{constructor(){const e=document.currentScript||(()=>{const n=document.getElementsByTagName("script");return n[n.length-1]})(),t=e&&e.dataset||{};this.configUrl=t.configUrl||f,this.config={watchdog_ms:this.numberOr(t.watchdogMs,15e3),ws_timeout:this.numberOr(t.wsTimeout,6e3),poll_timeout:this.numberOr(t.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:a(),viewId:a(),correlationId:a()},this.context=null,this.environmentId="",this.orchestrationType="",this.region="",this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.watchdogId=null,this.wsTimeoutId=null,this.settled=!1}run(){if(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window)||window.location.href.indexOf(l)===-1)return;const e=this.parseConfiguratorUrl();e&&(this.context={id:e.contextId,data:{configuratorId:e.configuratorId}},this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t)))}parseConfiguratorUrl(){const e=window.location.pathname.match(d);return e?{contextId:e[1].toLowerCase()==="modular"?"metaboxModularConfigurator":"metaboxBasicConfigurator",configuratorId:decodeURIComponent(e[2])}:null}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",this.fetchConfig().then(e=>{if(!e){this.fail("no-config");return}const t=this.chooseEndpoint(e);if(!t){this.fail("no-endpoint");return}if(!t.environmentId){this.fail("no-environment-id");return}this.orchestrationType=t.type,this.environmentId=t.environmentId;const i=this.adaptUrlToRegion(t.url);return(t.type==="polling"?this.pollOrchestration(i):Promise.resolve(i)).then(r=>{if(!r){this.fail("no-ws-url");return}return this.parked.machineId=r,this.openSocket(r)})})}fetchConfig(){const e=new URL(this.configUrl,window.location.origin).href;return this.fetchWithTimeout(e,{credentials:"omit"},this.config.poll_timeout).then(t=>t.ok?t.json().catch(()=>null):null).catch(()=>null)}chooseEndpoint(e){const t=e.newSignalling,i=this.firstVersionUrl(t&&t.versions);if(i&&/^wss?:\/\//i.test(i))return{type:"direct",url:i,environmentId:t&&t.environmentId||""};const n=e.signalling,r=this.firstVersionUrl(n&&n.versions);return r?{type:"polling",url:r,environmentId:n&&n.environmentId||""}:null}firstVersionUrl(e){if(!e||typeof e!="object")return"";for(const t of Object.keys(e))if(e[t])return e[t];return""}pollOrchestration(e){const t=`${e}${this.ids.clientId}/${this.ids.viewId}`;return this.fetchWithTimeout(t,{credentials:"omit"},this.config.poll_timeout).then(i=>i.ok?i.json().catch(()=>({})):null).then(i=>i&&i.signallingServer?m(`${window.location.protocol}//${i.signallingServer}`):null).catch(()=>null)}adaptUrlToRegion(e){return this.region&&e.indexOf(":region")===-1?e.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):e}openSocket(e){const t=new WebSocket(e);t.binaryType="arraybuffer",this.ws=t,this.parked.ws=t,this.wsTimeoutId=setTimeout(()=>{t.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),t.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),this.sendRequestStream()},t.onmessage=i=>this.onWsMessage(i),t.onerror=()=>this.fail("ws-error"),t.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&this.fail("ws-closed")},this.armWatchdog()}sendRequestStream(){this.send({type:"requestStream",correlationId:this.ids.correlationId,payload:{region:this.region,environmentId:this.environmentId,context:this.context}})}onWsMessage(e){let t;try{const i=typeof e.data=="string"?e.data:null;if(i===null)return;t=JSON.parse(i)}catch{return}switch(t.type){case"config":this.onConfig(t);break;case"offer":this.onOffer(t);break;case"answer":this.onAnswer(t);break;case"iceCandidate":this.onIceCandidate(t.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(e){if(this.pc)return;const t=Object.assign({},e.peerConnectionOptions);this.forceTURN&&(t.iceTransportPolicy="relay"),t.bundlePolicy=this.forceMaxBundle?"max-bundle":"balanced",t.iceCandidatePoolSize=1,this.parked.config=t;const i=new RTCPeerConnection(t);this.pc=i,this.parked.pc=i,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]&&(this.stream=n.streams[0],this.parked.stream=this.stream,this.maybeReady())}}onOffer(e){return this.pc?(this.remoteDescriptionSet=!1,this.iceBuffer.length=0,this.pc.setRemoteDescription(e).then(()=>(this.remoteDescriptionSet=!0,this.flushIce(),this.pc.createAnswer())).then(t=>(t.sdp=this.mungeSDP(t.sdp||""),this.pc.setLocalDescription(t).then(()=>{this.send(t)}))).catch(t=>this.fail("offer-failed",t))):Promise.resolve()}onAnswer(e){return this.pc?this.pc.setRemoteDescription(e).then(()=>{this.remoteDescriptionSet=!0,this.flushIce()}).catch(t=>this.fail("answer-failed",t)):Promise.resolve()}onIceCandidate(e){e&&(this.forceTURN&&String(e.candidate).indexOf("relay")<0||(this.remoteDescriptionSet&&this.pc?this.pc.addIceCandidate(e).catch(()=>{}):this.iceBuffer.push(e)))}flushIce(){for(const e of this.iceBuffer)this.pc.addIceCandidate(e).catch(()=>{});this.iceBuffer.length=0}wireDataChannel(e){e.binaryType="arraybuffer",this.dc=e,this.parked.dc=e,e.onmessage=t=>{this.parked.dcBuffer.push(t.data)},e.onopen=()=>{this.mark("dc_open"),this.maybeReady()},e.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&this.fail("dc-closed")},e.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")}publishParked(e){const t=this;this.parked={state:e,pc:null,dc:null,stream:null,ws:null,machineId:void 0,config:void 0,ids:this.ids,dcBuffer:[],timings:{},adopt(){return t.clearWatchdog(),this.state="adopted",this}},window.__stream=this.parked}armWatchdog(){this.clearWatchdog(),this.watchdogId=setTimeout(()=>{this.parked.state!=="adopted"&&this.fail("watchdog")},this.config.watchdog_ms)}clearWatchdog(){this.watchdogId&&(clearTimeout(this.watchdogId),this.watchdogId=null),this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null)}fail(e,t){if(!(this.parked&&this.parked.state==="adopted")){this.settled=!0,this.clearWatchdog();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=e,t&&(this.parked.failError=String(t&&t.message||t)))}}mungeSDP(e){let t=this.prioritizeH264Profile(e,p);t=t.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,`$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r
2
- `);let i="maxaveragebitrate=510000;";return i+=this.forceMonoAudio?"stereo=0;":"stereo=1;",i+="useinbandfec=1",t=t.replace("useinbandfec=1",i),t}prioritizeH264Profile(e,t){const i=e.split(`\r
3
- `),n=[];for(const r of i)if(r.startsWith("a=fmtp:")&&r.toLowerCase().includes(`profile-level-id=${t}`)){const s=r.split(":")[1]&&r.split(":")[1].split(" ")[0];s&&n.push(s)}return n.length===0?e:i.map(r=>{if(!r.startsWith("m=video"))return r;const s=r.split(" "),w=s.slice(0,3),h=s.slice(3),y=[...n.filter(c=>h.includes(c)),...h.filter(c=>!n.includes(c))];return[...w,...y].join(" ")}).join(`\r
4
- `)}send(e){const t=typeof e=="string"?e:JSON.stringify(e);if(this.ws&&this.ws.readyState===WebSocket.OPEN)try{this.ws.send(t)}catch{}}mark(e){this.parked&&(this.parked.timings[e]=performance.now())}readRegion(){try{const e=localStorage.getItem(u);if(!e)return null;const t=JSON.parse(e);return!t||typeof t.region_code!="string"||typeof t.ts!="number"||Date.now()-t.ts>=2592e6?null:t.region_code}catch{return null}}fetchWithTimeout(e,t,i){const n=new AbortController;t.signal=n.signal;const r=setTimeout(()=>n.abort(),i);return fetch(e,t).finally(()=>clearTimeout(r))}numberOr(e,t){const i=Number(e);return e!=null&&e!==""&&!Number.isNaN(i)?i:t}}new g().run()})();
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
+ `);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
+ `),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
+ `)}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.0",
3
+ "version": "0.0.146-dev.10",
4
4
  "description": "Unreal module for connect with unreal engine stream",
5
5
  "keywords": [
6
6
  "3dsource",
@@ -243,6 +243,16 @@ interface UnrealInitialConfig {
243
243
  autoHighResolution?: boolean;
244
244
  }
245
245
 
246
+ interface SignalingData {
247
+ instanceType: string;
248
+ branchInfo: string;
249
+ cirrusVersion: string;
250
+ streamerVersion: string;
251
+ streamPath: string;
252
+ streamApp: string;
253
+ connectionId: string;
254
+ }
255
+
246
256
  /**
247
257
  * Live WebRTC connection parked on `window.__stream` by the `stream-prefetch.js`
248
258
  * head-script, ready for the Angular services to ADOPT instead of building a
@@ -257,6 +267,8 @@ interface ParkedStream {
257
267
  stream?: MediaStream;
258
268
  /** live signalling socket (handshake already done, `requestStream` sent) */
259
269
  ws?: WebSocket;
270
+ /** `data` from the streamer's `ssInfo` message — received before adoption */
271
+ ssData?: SignalingData;
260
272
  /** = resolved wsUrl of the winning machine */
261
273
  machineId?: string;
262
274
  /** `peerConnectionOptions` the machine sent in its `config` message */
@@ -275,6 +287,13 @@ interface ParkedStream {
275
287
  failError?: string;
276
288
  /** Stops the script's watchdog and flips state to `adopted`. */
277
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. This is
293
+ * how {@link StreamAdoptionService} adopts event-driven (no polling) — the
294
+ * head-script must provide it.
295
+ */
296
+ onSettled: (cb: (state: ParkedStream['state']) => void) => void;
278
297
  }
279
298
  declare global {
280
299
  interface Window {
@@ -422,16 +441,6 @@ interface InputProps {
422
441
 
423
442
  type Quality = 'lime' | 'red' | 'orange';
424
443
 
425
- interface SignalingData {
426
- instanceType: string;
427
- branchInfo: string;
428
- cirrusVersion: string;
429
- streamerVersion: string;
430
- streamPath: string;
431
- streamApp: string;
432
- connectionId: string;
433
- }
434
-
435
444
  declare const OrchestrationMessageTypes: {
436
445
  readonly ping: "ping";
437
446
  readonly error: "error";
@@ -921,8 +930,15 @@ declare class RegionsPingService {
921
930
  * entry is well-formed and still within {@link REGION_PING_CACHE_TTL_MS}.
922
931
  * All access is guarded — `localStorage` can throw (private mode, disabled
923
932
  * storage) or be absent (non-browser).
933
+ *
934
+ * Public so adopted connections (which skip `getFastest`) can replay the
935
+ * region-resolving telemetry phase from the same cached winner — see
936
+ * `UnrealEffects.regionTelemetryOnAdopt$`.
924
937
  */
925
- private readCachedWinner;
938
+ readCachedWinner(): {
939
+ region_code: string;
940
+ result: number[];
941
+ } | null;
926
942
  private writeCachedWinner;
927
943
  /**
928
944
  * Accurate, OFF-critical-path refresh of the region cache. Mirrors the
@@ -1083,6 +1099,44 @@ declare class StreamAdoptionService {
1083
1099
  private readonly store;
1084
1100
  private readonly signalling;
1085
1101
  private readonly webRtc;
1102
+ /**
1103
+ * Decide adoption, WAITING for an in-flight prefetch instead of racing it.
1104
+ *
1105
+ * - nothing parked → emit `false` at once (caller connects normally).
1106
+ * - already settled (`ready`/`failed`/`adopted`) → adopt-if-ready at once.
1107
+ * - still `connecting` → wait for the script's `onSettled` callback and
1108
+ * adopt-if-ready the instant it fires (event-driven, zero polling latency).
1109
+ *
1110
+ * Deliberately NO time budget: for DIRECT orchestration the prefetch holds
1111
+ * the socket open while the orchestrator provisions a machine, which can take
1112
+ * minutes (150–200s+). A fixed deadline would make Angular give up and open a
1113
+ * second connection mid-provisioning — exactly the race/orphan this avoids.
1114
+ * The wait is bounded by liveness instead: the script flips the parked state
1115
+ * to `failed` on a dead socket (`ws.onclose`/`onerror`), and the caller wraps
1116
+ * this in `takeUntil(abortEstablishingConnection)`, so a route change / teardown
1117
+ * cancels it. Emits exactly once. Cancelling runs no adoption — which is what
1118
+ * stops a fast route-switch from starting overlapping connections.
1119
+ *
1120
+ * Wrapped in `defer` so the `tryAdopt()` side effect (which dispatches
1121
+ * `streamAdopted`) runs at SUBSCRIPTION time, not when the observable is
1122
+ * built. The effect prepends `startWith(setEstablishingConnection(true))`,
1123
+ * which starts the telemetry session and subscribes its phase catalog; that
1124
+ * must happen BEFORE `streamAdopted` → `regionTelemetryOnAdopt$` replays
1125
+ * `regionResolved`, or the region phase fires into a session that doesn't
1126
+ * exist yet and is lost. `defer` guarantees that ordering.
1127
+ */
1128
+ awaitAndAdopt$(): Observable<boolean>;
1129
+ /**
1130
+ * Event-driven wait: the script fires `onSettled` the instant it parks
1131
+ * `ready`/`failed`, so we adopt with zero polling lag. Emits exactly once.
1132
+ *
1133
+ * No timer/budget by design: an indefinitely `connecting` stream is valid
1134
+ * (DIRECT orchestration holds the socket open for minutes while it
1135
+ * provisions), and every terminal state — including a dead socket — flips to
1136
+ * `ready`/`failed` and fires the hook. Teardown (route change → unsubscribe)
1137
+ * sets `done`, so a late callback after cancel can't trigger a stray adoption.
1138
+ */
1139
+ private awaitSettled$;
1086
1140
  /**
1087
1141
  * @returns `true` if a ready parked stream was adopted (caller must NOT
1088
1142
  * start a fresh connection), `false` otherwise.
@@ -1179,6 +1233,17 @@ declare class StreamStatusTelemetryService2 {
1179
1233
  private readonly disconnectReason$;
1180
1234
  private readonly errorMessage$;
1181
1235
  private readonly viewPortReady$;
1236
+ /**
1237
+ * Synchronous adoption flag. The `streamAdopted` reducer sets it BEFORE any
1238
+ * action subscriber runs, so the connection-phase closers (which fire inside
1239
+ * the same dispatch, e.g. `ssinfo-received` off the `ssData` state change)
1240
+ * can read it and defer to the prefetch-timed attribution emitted by
1241
+ * {@link emitAdoptedConnection} instead of recording the live wall-clock.
1242
+ */
1243
+ private readonly adopted$;
1244
+ /** Streamer identity for the adopted signaling phase params. */
1245
+ private readonly ssData$;
1246
+ private readonly awsInstance$;
1182
1247
  private session;
1183
1248
  /**
1184
1249
  * Whether a session has already started during the current page lifetime.
@@ -1242,6 +1307,36 @@ declare class StreamStatusTelemetryService2 {
1242
1307
  * are derived from neighbor phases.
1243
1308
  */
1244
1309
  private emitPhaseEnded;
1310
+ /**
1311
+ * Emit the region / signaling / webrtc phases for an ADOPTED connection.
1312
+ *
1313
+ * The prefetch script established the connection in PARALLEL with page load,
1314
+ * so the phases are positioned at their real (anchor-relative) spans — shifted
1315
+ * LEFT to overlap the HTML-loading / route-idle rows — rather than sequentially
1316
+ * after them. That's the whole point of the prefetch: the handshake already
1317
+ * happened while the page was loading. Each phase ships:
1318
+ * - `duration` — the real sub-step time the prefetch spent.
1319
+ * - `spanStartMs` — anchor-relative start, so the viewer left-shifts the bar.
1320
+ * - `adoptionAtMs` — where Angular took over (the next phase, Unreal
1321
+ * communication, resumes here on the sequential timeline).
1322
+ * - savings params: `prefetchConnectMs` (full handshake), `prefetchSavedMs`
1323
+ * (done before Angular was ready), `adoptionWaitMs` (real connect→adopted
1324
+ * wait), `adoptionOverheadMs` (prefetch-ready → adopted gap).
1325
+ */
1326
+ private emitAdoptedConnection;
1327
+ /**
1328
+ * Emit one adopted phase positioned at its real `spanStartMs` (left-shifted
1329
+ * to overlap page load) with its real prefetch `duration`. `prefetched`,
1330
+ * `spanStartMs` and `adoptionAtMs` ride as top-level wire fields the viewer
1331
+ * uses to lay the bar out; the rest become display params.
1332
+ */
1333
+ private emitAdoptedPhase;
1334
+ /** Streamer identity params for the adopted signaling phase — mirrors the
1335
+ * `ssinfo-received` closer's shape (client / instanceName / …ssData). */
1336
+ private adoptedSignalingParams;
1337
+ /** Read the region-ping cache the prefetch reused (localStorage), so the
1338
+ * adopted region phase can still report the winning region + samples. */
1339
+ private readRegionCache;
1245
1340
  private handleWindow;
1246
1341
  private evaluateAnomalies;
1247
1342
  private updateMaxFpsTarget;
@@ -2553,8 +2648,30 @@ declare const initSignalling: node_modules__ngrx_store_types_ngrx_store.ActionCr
2553
2648
  */
2554
2649
  declare const streamAdopted: node_modules__ngrx_store_types_ngrx_store.ActionCreator<string, (props: {
2555
2650
  machineId: string | null;
2651
+ ssData: SignalingData | null;
2652
+ /**
2653
+ * `performance.now()` marks the prefetch script recorded while it
2654
+ * established the connection (`prefetch_start` / `ws_open` / `dc_open` /
2655
+ * `ready`), on the page navigation timeline. The telemetry service uses
2656
+ * them to attribute the signaling / webrtc phase durations to the time the
2657
+ * prefetch actually spent — instead of dumping the whole pre-adoption wait
2658
+ * onto whichever phase closer happens to fire first. `null` when adopting a
2659
+ * stream that carried no timings.
2660
+ */
2661
+ timings: Record<string, number> | null;
2556
2662
  }) => {
2557
2663
  machineId: string | null;
2664
+ ssData: SignalingData | null;
2665
+ /**
2666
+ * `performance.now()` marks the prefetch script recorded while it
2667
+ * established the connection (`prefetch_start` / `ws_open` / `dc_open` /
2668
+ * `ready`), on the page navigation timeline. The telemetry service uses
2669
+ * them to attribute the signaling / webrtc phase durations to the time the
2670
+ * prefetch actually spent — instead of dumping the whole pre-adoption wait
2671
+ * onto whichever phase closer happens to fire first. `null` when adopting a
2672
+ * stream that carried no timings.
2673
+ */
2674
+ timings: Record<string, number> | null;
2558
2675
  } & node_modules__ngrx_store_types_ngrx_store.Action<string>>;
2559
2676
  declare const startStream: node_modules__ngrx_store_types_ngrx_store.ActionCreator<string, (props: {
2560
2677
  config: Partial<StreamConfig>;
@@ -2770,7 +2887,9 @@ declare class UnrealEffects {
2770
2887
  id: string;
2771
2888
  now: number;
2772
2889
  } & node_modules__ngrx_store_types_ngrx_store.Action<string>> & node_modules__ngrx_effects_types_ngrx_effects.CreateEffectMetadata;
2773
- connectToSignaling$: node_modules_rxjs_dist_types.Observable<WebSocket> & node_modules__ngrx_effects_types_ngrx_effects.CreateEffectMetadata;
2890
+ connectToSignaling$: node_modules_rxjs_dist_types.Observable<{
2891
+ value: boolean;
2892
+ } & node_modules__ngrx_store_types_ngrx_store.Action<string>> & node_modules__ngrx_effects_types_ngrx_effects.CreateEffectMetadata;
2774
2893
  private canPeerReconnect;
2775
2894
  /**
2776
2895
  * Either schedules the next peer-reconnect attempt or gives up with the
@@ -2787,6 +2906,7 @@ declare const unrealFeature: {
2787
2906
  name: "unrealFeature";
2788
2907
  reducer: node_modules__ngrx_store_types_ngrx_store.ActionReducer<_3dsource_angular_unreal_module.UnrealState, node_modules__ngrx_store_types_ngrx_store.Action<string>>;
2789
2908
  selectUnrealFeatureState: node_modules__ngrx_store_types_ngrx_store.MemoizedSelector<Record<string, any>, _3dsource_angular_unreal_module.UnrealState, (featureState: _3dsource_angular_unreal_module.UnrealState) => _3dsource_angular_unreal_module.UnrealState>;
2909
+ selectAdopted: node_modules__ngrx_store_types_ngrx_store.MemoizedSelector<Record<string, any>, boolean, (featureState: _3dsource_angular_unreal_module.UnrealState) => boolean>;
2790
2910
  selectFreezeFrame: node_modules__ngrx_store_types_ngrx_store.MemoizedSelector<Record<string, any>, _3dsource_angular_unreal_module.FreezeFrameMessage, (featureState: _3dsource_angular_unreal_module.UnrealState) => _3dsource_angular_unreal_module.FreezeFrameMessage>;
2791
2911
  selectIsVideoPlaying: node_modules__ngrx_store_types_ngrx_store.MemoizedSelector<Record<string, any>, boolean, (featureState: _3dsource_angular_unreal_module.UnrealState) => boolean>;
2792
2912
  selectStatusMessage: node_modules__ngrx_store_types_ngrx_store.MemoizedSelector<Record<string, any>, string | null, (featureState: _3dsource_angular_unreal_module.UnrealState) => string | null>;
@@ -2843,6 +2963,13 @@ interface UnrealState {
2843
2963
  isAfkTimerVisible: boolean;
2844
2964
  afkCountdown: number;
2845
2965
  cirrusConnected: boolean;
2966
+ /**
2967
+ * True once a prefetched connection has been adopted (`streamAdopted`).
2968
+ * Read synchronously by the telemetry service so the connection-phase
2969
+ * closers know to defer to the prefetch-timed attribution instead of the
2970
+ * live-event one. Reset on every fresh attempt / disconnect.
2971
+ */
2972
+ adopted: boolean;
2846
2973
  viewportReady: boolean;
2847
2974
  freezeFrameFromVideo: FreezeFrameMessage;
2848
2975
  freezeFrame: FreezeFrameMessage;