@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.
- package/LICENSE +14 -14
- package/README.md +229 -229
- package/fesm2022/3dsource-angular-unreal-module.mjs +179 -204
- package/fesm2022/3dsource-angular-unreal-module.mjs.map +1 -1
- package/js/stream-prefetch.js +4 -4
- package/package.json +1 -1
- package/types/3dsource-angular-unreal-module.d.ts +68 -82
package/js/stream-prefetch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";(()=>{"use strict";const g=/metabox-configurator\/(modular|basic)\//i,m="features",k="
|
|
2
|
-
`);let i="maxaveragebitrate=510000;";return i+=this.forceMonoAudio?"stereo=0;":"stereo=1;",i+="useinbandfec=1",
|
|
3
|
-
`),
|
|
4
|
-
`)}send(
|
|
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
|
@@ -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:
|
|
1374
|
-
* {@link
|
|
1375
|
-
*
|
|
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
|
|
1383
|
-
* `
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
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
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
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
|
|
1427
|
-
* `
|
|
1428
|
-
*
|
|
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
|
|
1435
|
-
*
|
|
1436
|
-
* `
|
|
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
|
-
*
|
|
1446
|
-
* {@link
|
|
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
|
-
*
|
|
1454
|
-
* failures of the request
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
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
|
|
1472
|
-
*
|
|
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
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
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
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
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
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
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
|
|
1515
|
-
*
|
|
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
|
-
*
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
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
|
|
1529
|
-
* `
|
|
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
|
-
*
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
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;
|