@3dsource/angular-unreal-module 0.0.169 → 0.0.171

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 g=/metabox-configurator\/(modular|basic)\//i,m="features",k="overrideSignallingUrl",w="3dsource:region-ping",y="assets/config.json",f="https://datacenter.3dsource.com/signallingserver",b="42e01f";function l(){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 S(o){return o.replace("http://","ws://").replace("https://","wss://")}const h="[stream-prefetch]",n=(...o)=>console.log(h,...o),c=(...o)=>console.warn(h,...o),u=(...o)=>console.error(h,...o);class T{constructor(){const e=document.currentScript||(()=>{const r=document.getElementsByTagName("script");return r[r.length-1]})(),t=e&&e.dataset||{};this.configUrl=t.configUrl||y,this.fallbackPollingUrl=t.fallbackUrl||"",this.skipRouteCheck=this.boolOr(t.skipRouteCheck,!1),this.config={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:l(),viewId:l(),correlationId:l(),streamRequestId:""},this.region="",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))){if(this.isNewOrchestrationEnabled()){n("skip \u2014 features.newOrchestration is enabled");return}if(this.skipRouteCheck){c("skipRouteCheck=true \u2014 ignoring URL gate; using fallback polling"),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e));return}if(!g.test(window.location.href)){n("skip \u2014 not a metabox-configurator/{modular|basic} route");return}n("start",{ids:this.ids}),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e))}}connect(){this.mark("prefetch_start"),this.region=this.readRegion()||"",n("region from cache:",this.region||"(none)");const e=this.readSignallingUrlOverride();return(e||this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(i=>{let r;if(e)n("signalling URL overridden via localStorage",e),r={type:"polling",url:e};else if(i){if(r=this.chooseEndpoint(i),r&&!this.isAllowedEndpoint(r.url)){n("skip \u2014 signalling URL not sanctioned for prefetch",r.url),this.fail("endpoint-not-allowed");return}}else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),r={type:"polling",url:this.fallbackPollingUrl};else{this.fail("no-config");return}if(!r){this.fail("no-endpoint");return}n("endpoint chosen",{type:r.type,url:r.url});const s=this.adaptUrlToRegion(r.url);return this.pollOrchestration(s).then(a=>{if(!a){this.fail("no-ws-url");return}return this.parked.machineId=a,this.parked.pollingUrl=s,n("opening socket",a),this.openSocket(a)})})}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.signalling,i=this.firstVersionUrl(t&&t.versions);return i?{type:"polling",url:i}:null}isAllowedEndpoint(e){if(typeof e!="string")return!1;const t=e.replace(/\/+$/,"");return t===f||t.startsWith(f+"/")}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?null:(this.ids.streamRequestId=i.streamRequestId||"",S(`${window.location.protocol}//${i.signallingServer}`))).catch(()=>null)}adaptUrlToRegion(e){return this.region&&e.indexOf(":region")===-1?e.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):e}openSocket(e){const t=this.ids.streamRequestId,i=t?e+(e.indexOf("?")===-1?"?":"&")+"streamRequestId="+encodeURIComponent(t):e,r=new WebSocket(i);r.binaryType="arraybuffer",this.ws=r,this.parked.ws=r,this.wsTimeoutId=setTimeout(()=>{r.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),r.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),n("WS open \u2014 waiting for config",{region:this.region,streamRequestId:t||"(none)"})},r.onmessage=s=>this.onWsMessage(s),r.onerror=s=>{u("WS error",s),this.fail("ws-error")},r.onclose=s=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:s&&s.code}),this.fail("ws-closed"))}}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(n("WS \u2190",t.type),t.type){case"ssInfo":this.parked.ssData=t.data;break;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;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,n("PeerConnection created from config"),i.onicecandidate=r=>{r.candidate&&this.send({type:"iceCandidate",candidate:r.candidate})},i.ondatachannel=r=>this.wireDataChannel(r.channel),i.ontrack=r=>{r.track&&r.track.kind==="video"&&r.streams[0]&&(n("video track received"),this.stream=r.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(()=>{n("WS \u2192 answer sent"),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=()=>{n("DataChannel open"),this.mark("dc_open"),this.maybeReady()},e.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&(c("DataChannel closed before 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",n("READY \u2014 parked on window.__stream",this.parked.timings),this.fireSettled())}publishParked(e){const t=this;this.parked={state:e,pc:null,dc:null,stream:null,ws:null,ssData:void 0,machineId:void 0,pollingUrl:void 0,config:void 0,ids:this.ids,dcBuffer:[],timings:{},adopt(){return t.clearTimers(),this.state="adopted",this},onSettled(i){if(typeof i=="function"){if(this.state!=="connecting"){const r=this.state;Promise.resolve().then(()=>i(r));return}t.settledCb=i}}},window.__stream=this.parked}fireSettled(){const e=this.settledCb;if(e){this.settledCb=null;try{e(this.parked.state)}catch{}}}clearTimers(){this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null)}fail(e,t){if(!(this.parked&&(this.parked.state==="adopted"||this.parked.state==="failed"))){u("FAILED:",e,t||""),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=e,t&&(this.parked.failError=String(t&&t.message||t)),this.fireSettled())}}mungeSDP(e){let t=this.prioritizeH264Profile(e,b);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
- `),r=[];for(const s of i)if(s.startsWith("a=fmtp:")&&s.toLowerCase().includes(`profile-level-id=${t}`)){const a=s.split(":")[1]&&s.split(":")[1].split(" ")[0];a&&r.push(a)}return r.length===0?e:i.map(s=>{if(!s.startsWith("m=video"))return s;const a=s.split(" "),I=a.slice(0,3),p=a.slice(3),R=[...r.filter(d=>p.includes(d)),...p.filter(d=>!r.includes(d))];return[...I,...R].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())}isNewOrchestrationEnabled(){try{const e=localStorage.getItem(m);if(!e)return!1;const t=JSON.parse(e);return!!(t&&t.newOrchestration===!0)}catch{return!1}}readSignallingUrlOverride(){try{const e=localStorage.getItem(k);if(!e)return"";const t=e.charAt(0)==='"'?JSON.parse(e):e;return typeof t=="string"?t.trim():""}catch{return""}}readRegion(){try{const e=localStorage.getItem(w);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 r=new AbortController;t.signal=r.signal;const s=setTimeout(()=>r.abort(),i);return fetch(e,t).finally(()=>clearTimeout(s))}numberOr(e,t){const i=Number(e);return e!=null&&e!==""&&!Number.isNaN(i)?i:t}boolOr(e,t){return e==="true"||e===""?!0:e==="false"?!1:t}}new T().run()})();
1
+ "use strict";(()=>{"use strict";const g=/metabox-configurator\/(modular|basic)\//i,m="features",k="3dsource:region-ping",w="assets/config.json",f="https://datacenter.3dsource.com/signallingserver",y="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 b(o){return o.replace("http://","ws://").replace("https://","wss://")}const h="[stream-prefetch]",r=(...o)=>console.log(h,...o),c=(...o)=>console.warn(h,...o),u=(...o)=>console.error(h,...o);class T{constructor(){const t=document.currentScript||(()=>{const s=document.getElementsByTagName("script");return s[s.length-1]})(),e=t&&t.dataset||{};this.configUrl=e.configUrl||w,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(),streamRequestId:""},this.region="",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))){if(this.isNewOrchestrationEnabled()){r("skip \u2014 features.newOrchestration is enabled");return}if(this.skipRouteCheck){c("skipRouteCheck=true \u2014 ignoring URL gate; using fallback polling"),this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t));return}if(!g.test(window.location.href)){r("skip \u2014 not a metabox-configurator/{modular|basic} route");return}r("start",{ids:this.ids}),this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t))}}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",r("region from cache:",this.region||"(none)"),(this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(e=>{let i;if(e){if(i=this.chooseEndpoint(e),i&&!this.isAllowedEndpoint(i.url)){r("skip \u2014 signalling URL not sanctioned for prefetch",i.url),this.fail("endpoint-not-allowed");return}}else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),i={type:"polling",url:this.fallbackPollingUrl};else{this.fail("no-config");return}if(!i){this.fail("no-endpoint");return}r("endpoint chosen",{type:i.type,url:i.url});const s=this.adaptUrlToRegion(i.url);return this.pollOrchestration(s).then(n=>{if(!n){this.fail("no-ws-url");return}return this.parked.machineId=n,this.parked.pollingUrl=s,r("opening socket",n),this.openSocket(n)})})}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.signalling,i=this.firstVersionUrl(e&&e.versions);return i?{type:"polling",url:i}:null}isAllowedEndpoint(t){if(typeof t!="string")return!1;const e=t.replace(/\/+$/,"");return e===f||e.startsWith(f+"/")}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?null:(this.ids.streamRequestId=i.streamRequestId||"",b(`${window.location.protocol}//${i.signallingServer}`))).catch(()=>null)}adaptUrlToRegion(t){return this.region&&t.indexOf(":region")===-1?t.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):t}openSocket(t){const e=this.ids.streamRequestId,i=e?t+(t.indexOf("?")===-1?"?":"&")+"streamRequestId="+encodeURIComponent(e):t,s=new WebSocket(i);s.binaryType="arraybuffer",this.ws=s,this.parked.ws=s,this.wsTimeoutId=setTimeout(()=>{s.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),s.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),r("WS open \u2014 waiting for config",{region:this.region,streamRequestId:e||"(none)"})},s.onmessage=n=>this.onWsMessage(n),s.onerror=n=>{u("WS error",n),this.fail("ws-error")},s.onclose=n=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:n&&n.code}),this.fail("ws-closed"))}}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(r("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;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,r("PeerConnection created from config"),i.onicecandidate=s=>{s.candidate&&this.send({type:"iceCandidate",candidate:s.candidate})},i.ondatachannel=s=>this.wireDataChannel(s.channel),i.ontrack=s=>{s.track&&s.track.kind==="video"&&s.streams[0]&&(r("video track received"),this.stream=s.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(()=>{r("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=()=>{r("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",r("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,pollingUrl: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 s=this.state;Promise.resolve().then(()=>i(s));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,y);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
+ `),s=[];for(const n of i)if(n.startsWith("a=fmtp:")&&n.toLowerCase().includes(`profile-level-id=${e}`)){const a=n.split(":")[1]&&n.split(":")[1].split(" ")[0];a&&s.push(a)}return s.length===0?t:i.map(n=>{if(!n.startsWith("m=video"))return n;const a=n.split(" "),x=a.slice(0,3),p=a.slice(3),S=[...s.filter(d=>p.includes(d)),...p.filter(d=>!s.includes(d))];return[...x,...S].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())}isNewOrchestrationEnabled(){try{const t=localStorage.getItem(m);if(!t)return!1;const e=JSON.parse(t);return!!(e&&e.newOrchestration===!0)}catch{return!1}}readRegion(){try{const t=localStorage.getItem(k);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 s=new AbortController;e.signal=s.signal;const n=setTimeout(()=>s.abort(),i);return fetch(t,e).finally(()=>clearTimeout(n))}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 T().run()})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3dsource/angular-unreal-module",
3
- "version": "0.0.169",
3
+ "version": "0.0.171",
4
4
  "description": "Angular integration for Unreal Engine Pixel Streaming",
5
5
  "keywords": [
6
6
  "3dsource",
@@ -1362,6 +1362,7 @@ declare class SignallingService extends SubService {
1362
1362
  abort$: Observable<_ngrx_store.Action<string>>;
1363
1363
  private ws;
1364
1364
  private controlWs;
1365
+ private handedOverControlWs;
1365
1366
  orchestrationRetrySub?: Subscription;
1366
1367
  orchestrationRetryCount: number;
1367
1368
  private wsMsgHandlers;
@@ -1370,43 +1371,26 @@ declare class SignallingService extends SubService {
1370
1371
  protected init(): void;
1371
1372
  connectToSignaling(urlList: string[]): Observable<WebSocket>;
1372
1373
  /**
1373
- * One inner cycle: rotate `urlGen` over the pool, resolve via
1374
- * {@link requestCirrus}, open WS via {@link connectToCirrus},
1375
- * retry up to {@link INNER_RETRY_LIMIT} on any hard failure. The
1376
- * urlGen lives in this closure so retries advance through the
1377
- * pool. Exhaustion bubbles to {@link connectToSignaling}'s outer
1378
- * retry which re-pings the region.
1374
+ * One inner cycle: {@link requestCirrus} → {@link connectToCirrus}, retried up to
1375
+ * {@link INNER_RETRY_LIMIT}. `urlGen` lives in this closure so retries advance
1376
+ * through the pool; exhaustion bubbles to the outer region-repick retry.
1379
1377
  */
1380
1378
  private connectAttempt;
1381
1379
  /**
1382
- * Status message for the connect branch. It does NOT dispatch
1383
- * `setEstablishingConnection` — `UnrealEffects.connectToSignaling$` already
1384
- * emits that once per logical attempt via `startWith(...)`, covering BOTH
1385
- * branches (prefetch adoption and a fresh connect). Dispatching it here as
1386
- * well fired the boundary twice on every cold start, and since
1387
- * `StreamStatusTelemetryService2` starts a session on that boundary, the
1388
- * first session was killed 2 ms in as `restart` — losing the only honest
1389
- * page-load measurement and the prefetch adoption with it.
1380
+ * Status message only — must NOT dispatch `setEstablishingConnection`, which
1381
+ * `UnrealEffects.connectToSignaling$` already emits once per logical attempt for
1382
+ * both branches. Dispatching it here too fired that boundary twice on cold start,
1383
+ * and `StreamStatusTelemetryService2` starts a session on it — so the first
1384
+ * session died 2 ms in as `restart`, taking the page-load measurement with it.
1390
1385
  */
1391
1386
  protected startEstablishingConnection(): void;
1392
1387
  private adaptUrlsToRegion;
1393
1388
  /**
1394
- * Ask the orchestration for a Cirrus to connect to. All three
1395
- * implementations answer the same question — "which Cirrus, and with which
1396
- * `streamRequestId`?" — and emit ONE `AwsInstance` whose `wsUrl` is a Cirrus
1397
- * address. What differs is only how the answer is obtained and therefore how
1398
- * it can fail:
1399
- *
1400
- * | Method | Transport | Fails on |
1401
- * | -------------------------- | -------------------- | ------------------------------ |
1402
- * | {@link requestCirrusViaPolling} | HTTP poll loop | HTTP 4xx (dead URL) |
1403
- * | {@link requestCirrusViaSocket} | control WS | socket drop / no `instanceReady` in {@link INSTANCE_READY_TIMEOUT} |
1404
- * | {@link requestCirrusDirect} | none (dev) | never — nothing is requested |
1405
- *
1406
- * Whatever they return goes through the SAME downstream in
1407
- * {@link connectAttempt}: `setAwsInstance` → `connectToCirrus` → the WS-only
1408
- * and URL-rotating retry tiers. A failure inside any of them is just an
1409
- * error on this observable, so those tiers handle it uniformly.
1389
+ * Ask the orchestration for a Cirrus to connect to. All three implementations
1390
+ * answer the same question — which Cirrus, with which `streamRequestId` — and emit
1391
+ * ONE `AwsInstance` whose `wsUrl` is a Cirrus address, so {@link connectAttempt}
1392
+ * treats them uniformly: a failure in any of them is just an error here, which its
1393
+ * retry tiers already handle. Per-transport failure surface: SIGNALLING.md.
1410
1394
  */
1411
1395
  private requestCirrus;
1412
1396
  /** True when `requestStream` can be composed → new orchestration. */
@@ -1423,38 +1407,32 @@ declare class SignallingService extends SubService {
1423
1407
  */
1424
1408
  private requestCirrusDirect;
1425
1409
  /**
1426
- * HTTP-orchestration path. Brackets {@link pollLoop} with the
1427
- * `pollingStarted`/`pollingEnded` actions and the `EXT-getSignaling`
1428
- * telemetry span (start in `defer`, stop in `finalize` with the
1429
- * last response captured in the closure). Maps the final ready
1430
- * response to an `AwsInstance`.
1410
+ * HTTP-orchestration path. Brackets {@link pollLoop} with `pollingStarted`/
1411
+ * `pollingEnded` and the `EXT-getSignaling` telemetry span (started in `defer`,
1412
+ * stopped in `finalize` with the last response from the closure).
1431
1413
  */
1432
1414
  private requestCirrusViaPolling;
1433
1415
  /**
1434
- * WS-orchestration path — the socket-transport twin of
1435
- * {@link requestCirrusViaPolling}. Opens the control socket, sends
1436
- * `requestStream` and waits for `instanceReady`, whose `originInstanceUri`
1437
- * IS the Cirrus address.
1438
- *
1439
- * Failure modes are handled here so they surface as a plain error and reach
1440
- * `connectAttempt`'s retry tiers (previously this branch lived outside them
1441
- * and a dead control socket silently hung the session):
1442
- * - control socket closes/errors before `instanceReady` → error
1443
- * - no `instanceReady` within {@link INSTANCE_READY_TIMEOUT} → error
1416
+ * WS-orchestration path — the socket twin of {@link requestCirrusViaPolling}.
1417
+ * Opens the control socket, sends `requestStream`, waits for `instanceReady`
1418
+ * whose `originInstanceUri` IS the Cirrus address.
1444
1419
  *
1445
- * The control socket lives exactly as long as this wait — see
1446
- * {@link awaitInstanceReady}.
1420
+ * Both failure modes — socket drop before `instanceReady`, or no `instanceReady`
1421
+ * within {@link INSTANCE_READY_TIMEOUT} — must surface as a plain error so
1422
+ * `connectAttempt`'s retry tiers see them; without that a dead control socket
1423
+ * hangs the session silently. See {@link awaitInstanceReady}.
1447
1424
  */
1448
1425
  private requestCirrusViaSocket;
1449
1426
  /**
1450
1427
  * Send `requestStream` on the freshly opened control socket and resolve with
1451
1428
  * the machine coordinates from `instanceReady`.
1452
1429
  *
1453
- * The socket lives exactly as long as this wait: its close/error are HARD
1454
- * failures of the request, and on resolution it is closed with every handler
1455
- * detached (the backend does not use it after the handover). So the control
1456
- * socket has no post-handover state at all — nothing that could report a stale
1457
- * "disconnect" against a live stream.
1430
+ * A race between one answer and two failure signals — until `instanceReady` the
1431
+ * socket's close/error are HARD failures of the request. On success it is handed
1432
+ * over, not closed: {@link handOverControlWs}.
1433
+ *
1434
+ * `on*` properties rather than `fromEvent`: that is how the rest of the file
1435
+ * attaches, and how `teardownSocket` detaches.
1458
1436
  */
1459
1437
  private awaitInstanceReady;
1460
1438
  /**
@@ -1468,38 +1446,49 @@ declare class SignallingService extends SubService {
1468
1446
  * | HTTP 4xx (except 429) | ❌ Hard | Re-throw → caller rotates URL |
1469
1447
  * | HTTP 5xx / 429 / timeout / network | ⚠️ Soft | `EMPTY` → `repeat` same URL |
1470
1448
  *
1471
- * `data.error` is NOT treated as fatal — the backend reuses it for
1472
- * progress strings like `"Ready in ~2m 23s"`. The only fatal exits
1473
- * are HTTP 4xx and `abort$` upstream.
1449
+ * `data.error` is NOT fatal — the backend reuses it for progress strings like
1450
+ * `"Ready in ~2m 23s"`. Only HTTP 4xx and upstream `abort$` are fatal.
1474
1451
  */
1475
1452
  private pollLoop;
1476
1453
  private connectToCirrus;
1477
1454
  /**
1478
- * Handlers for the CIRRUS socket — the only long-lived socket. Its close/error
1479
- * are real stream events (`setCirrusDisconnected` / `dropConnection`), unlike
1480
- * the control socket's, which never outlives {@link awaitInstanceReady}.
1455
+ * Handlers for the CIRRUS socket — the only socket whose close/error are real
1456
+ * stream events (`setCirrusDisconnected` / `dropConnection`). The control socket
1457
+ * outlives {@link awaitInstanceReady} too, but with a log-only `onclose`.
1481
1458
  */
1482
1459
  private addWsHandlers;
1483
1460
  /**
1484
- * Adopt a live signalling socket opened by the `stream-prefetch.js`
1485
- * head-script. The socket is already OPEN and `requestStream` was already
1486
- * sent, so — unlike {@link addWsHandlers} — we attach message/close/error
1487
- * handlers but deliberately skip {@link OnOpenHandler} (which would
1488
- * `setCirrusConnected` + re-`sendRequestStream`). Reuses the head-script's
1489
- * `correlationId` so subsequent correlated messages line up.
1461
+ * Adopt a live socket from the `stream-prefetch.js` head-script. Already OPEN with
1462
+ * `requestStream` sent, so unlike {@link addWsHandlers} this skips
1463
+ * {@link OnOpenHandler} (which would re-`setCirrusConnected`). Reuses the
1464
+ * head-script's `correlationId` so correlated messages line up.
1490
1465
  */
1491
1466
  adoptSocket(ws: WebSocket, correlationId: string): void;
1492
1467
  private showStatusMessage;
1493
1468
  /**
1494
- * Close everything: the Cirrus socket AND the orchestration control socket.
1495
- * Used on abort/teardown. A new connection attempt inside a live cycle uses
1496
- * {@link closeCirrus} instead, so it does not kill the control socket that
1497
- * produced the address it is about to connect to.
1469
+ * Abort/teardown: closes the Cirrus socket AND the control one, handed over or
1470
+ * not (`abort$` fans in `setCirrusDisconnected` / `dropConnection` /
1471
+ * `destroyUnrealScene`). A new attempt inside a live cycle uses
1472
+ * {@link closeCirrus}, so it does not kill the socket that produced the address
1473
+ * it is about to connect to.
1498
1474
  */
1499
1475
  close(data: CloseReason): void;
1500
1476
  /** Close only the Cirrus signalling socket. */
1501
1477
  private closeCirrus;
1478
+ /**
1479
+ * No control socket survives this — the live one AND one left open by a handover,
1480
+ * so callers need no extra knowledge about which state it is in.
1481
+ */
1502
1482
  private teardownControlWs;
1483
+ /**
1484
+ * Handover, not teardown: strip the handlers, leave the socket OPEN for
1485
+ * orchestration to close (SIGNALLING.md). Moving it out of `controlWs` is what
1486
+ * closes the send path; dropping `onmessage` is what stops a late `error`
1487
+ * (4001/4002) from arming a resend and reserving a second machine.
1488
+ *
1489
+ * Takes the socket explicitly so it is provably the caller's.
1490
+ */
1491
+ private handOverControlWs;
1503
1492
  private teardownSocket;
1504
1493
  /**
1505
1494
  * Player signalling (offer/answer/iceCandidate/pong/p2pEstablished/subscribe)
@@ -1511,31 +1500,28 @@ declare class SignallingService extends SubService {
1511
1500
  private sendTo;
1512
1501
  protected handleMessage(msg: MessageBase): void;
1513
1502
  /**
1514
- * One handler map shared by both sockets — `handleMessage` dispatches purely by
1515
- * message type. Which socket a type can arrive on is fixed, so registration is
1516
- * split by origin: {@link setOrchestrationHandlers} (control socket) and
1517
- * {@link setStreamHandlers} (Cirrus).
1503
+ * One map shared by both sockets — `handleMessage` dispatches purely by type, so
1504
+ * registration is split by origin only for readability.
1518
1505
  *
1519
- * Keepalive is deliberately absent: both servers ping at the WS **protocol**
1520
- * level (`ws.ping()`), which the browser answers on its own — the page never
1521
- * sees a ping and must never reply with an application-level `pong`. An
1522
- * unregistered type is dropped silently by `handleMessage`, so a `ping` from a
1523
- * not-yet-updated backend is simply ignored.
1506
+ * Keepalive is deliberately absent: both servers ping at the WS **protocol** level,
1507
+ * which the browser answers itself, so the page must never reply with an
1508
+ * application-level `pong`. An unregistered type is dropped silently, so a `ping`
1509
+ * from a not-yet-updated backend is simply ignored.
1524
1510
  */
1525
1511
  private setMessageHandlers;
1526
1512
  /**
1527
1513
  * Messages from the ORCHESTRATION control socket. Only reachable during the
1528
- * provisioning wait — `requestCirrusViaSocket` closes that socket the moment
1529
- * `instanceReady` lands.
1514
+ * provisioning wait: the socket outlives `instanceReady`, but `handOverControlWs`
1515
+ * drops its `onmessage`, so nothing routes here afterwards.
1530
1516
  */
1531
1517
  private setOrchestrationHandlers;
1532
1518
  /** Messages from the CIRRUS socket — the actual stream signalling. */
1533
1519
  private setStreamHandlers;
1534
1520
  protected sendRequestStream(): void;
1535
1521
  /**
1536
- * Is the player-signalling socket open? Always the Cirrus socket — the
1537
- * control socket's state is irrelevant to the stream (orchestration closes it
1538
- * once the instance is occupied), so it must not be part of this gate.
1522
+ * Cirrus only — the control socket's state says nothing about the stream
1523
+ * (orchestration closes it once the instance is occupied), so it must stay out
1524
+ * of this gate.
1539
1525
  */
1540
1526
  isConnected(): boolean;
1541
1527
  private getRegion;