@3dsource/angular-unreal-module 0.0.146-dev.4 → 0.0.146-dev.5

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.
@@ -1670,6 +1670,10 @@ class RegionsPingService {
1670
1670
  * entry is well-formed and still within {@link REGION_PING_CACHE_TTL_MS}.
1671
1671
  * All access is guarded — `localStorage` can throw (private mode, disabled
1672
1672
  * storage) or be absent (non-browser).
1673
+ *
1674
+ * Public so adopted connections (which skip `getFastest`) can replay the
1675
+ * region-resolving telemetry phase from the same cached winner — see
1676
+ * `UnrealEffects.regionTelemetryOnAdopt$`.
1673
1677
  */
1674
1678
  readCachedWinner() {
1675
1679
  try {
@@ -3598,14 +3602,19 @@ class StreamAdoptionService {
3598
3602
  *
3599
3603
  * - nothing parked → emit `false` at once (caller connects normally).
3600
3604
  * - already settled (`ready`/`failed`/`adopted`) → adopt-if-ready at once.
3601
- * - still `connecting` → poll until it settles, then adopt-if-ready. Bounded
3602
- * by `timeoutMs` so a stuck prefetch can't block the fallback forever.
3605
+ * - still `connecting` → poll until the script settles it, then adopt-if-ready.
3603
3606
  *
3604
- * Emits exactly once. Cancel by unsubscribing (the effect does this on
3605
- * teardown via `takeUntil`) — no adoption runs after cancellation, which is
3606
- * what stops a fast route-switch from starting overlapping connections.
3607
+ * Deliberately NO time budget: for DIRECT orchestration the prefetch holds
3608
+ * the socket open while the orchestrator provisions a machine, which can take
3609
+ * minutes (150–200s+). A fixed deadline would make Angular give up and open a
3610
+ * second connection mid-provisioning — exactly the race/orphan this avoids.
3611
+ * The wait is bounded by liveness instead: the script flips the parked state
3612
+ * to `failed` on a dead socket (`ws.onclose`/`onerror`), and the caller wraps
3613
+ * this in `takeUntil(abortEstablishingConnection)`, so a route change / teardown
3614
+ * cancels it. Emits exactly once. Cancelling runs no adoption — which is what
3615
+ * stops a fast route-switch from starting overlapping connections.
3607
3616
  */
3608
- awaitAndAdopt$(timeoutMs) {
3617
+ awaitAndAdopt$() {
3609
3618
  const parked = window.__stream;
3610
3619
  if (!parked) {
3611
3620
  return of(false);
@@ -3615,9 +3624,9 @@ class StreamAdoptionService {
3615
3624
  }
3616
3625
  Logger.info('[Adoption] Prefetch in progress — waiting before connecting');
3617
3626
  return timer(0, ADOPT_POLL_MS).pipe(map$1(() => window.__stream?.state ?? 'failed'),
3618
- // Keep polling while still connecting AND within budget; the `true`
3619
- // (inclusive) emits the settling value (or the last one at the deadline).
3620
- takeWhile((state, i) => state === 'connecting' && i * ADOPT_POLL_MS < timeoutMs, true), last(), map$1(() => this.tryAdopt()));
3627
+ // Keep polling while still connecting; the `true` (inclusive) emits the
3628
+ // settling value (`ready`/`failed`) that ends the wait.
3629
+ takeWhile((state) => state === 'connecting', true), last(), map$1(() => this.tryAdopt()));
3621
3630
  }
3622
3631
  /**
3623
3632
  * @returns `true` if a ready parked stream was adopted (caller must NOT
@@ -4197,9 +4206,7 @@ class UnrealEffects {
4197
4206
  // connection; otherwise fall back to a fresh signalling connection.
4198
4207
  // `takeUntil(abort)` cancels the wait on teardown / route change so a
4199
4208
  // fast switch can't leave overlapping connections.
4200
- this.streamAdoptionService
4201
- .awaitAndAdopt$(this.dataChannelConnectionTimeout)
4202
- .pipe(switchMap((adopted) => adopted ? EMPTY : this.signallingService.connectToSignaling(urls)), takeUntil(this.actions$.pipe(ofType(abortEstablishingConnection))),
4209
+ this.streamAdoptionService.awaitAndAdopt$().pipe(switchMap((adopted) => adopted ? EMPTY : this.signallingService.connectToSignaling(urls)), takeUntil(this.actions$.pipe(ofType(abortEstablishingConnection))),
4203
4210
  // The adopt/connect stream is side-effect-only (its values aren't
4204
4211
  // actions) — swallow them, and emit `setEstablishingConnection`
4205
4212
  // first so the reconnect/resume popup stays hidden during the
@@ -4207,6 +4214,24 @@ class UnrealEffects {
4207
4214
  // defers `connectToSignaling`, which is what normally sets this.
4208
4215
  ignoreElements(), startWith(setEstablishingConnection({ value: true })))));
4209
4216
  });
4217
+ // Adoption skips `connectToSignaling → getRegion`, so the region-resolving
4218
+ // telemetry phase never fires. Replay the region waterfall (start → end →
4219
+ // resolved) from the cached winner the prefetch reused — a near-zero-length
4220
+ // phase that still carries the ping samples. No live ping sweep; if there is
4221
+ // no cache the resolved event just carries empty values.
4222
+ this.regionTelemetryOnAdopt$ = createEffect(() => {
4223
+ return this.actions$.pipe(ofType(streamAdopted), mergeMap(() => {
4224
+ const cached = this.regionsPingService.readCachedWinner();
4225
+ return [
4226
+ regionPingFetchStart(),
4227
+ regionPingFetchEnd(),
4228
+ regionResolved({
4229
+ region: cached?.region_code ?? '',
4230
+ pingsMs: cached?.result ?? [],
4231
+ }),
4232
+ ];
4233
+ }));
4234
+ });
4210
4235
  }
4211
4236
  canPeerReconnect() {
4212
4237
  return RECONNECT_CONFIG.enabled && this.signallingService.isConnected();
@@ -5615,17 +5640,13 @@ class StreamStatusTelemetryService2 {
5615
5640
  // CLOSER — the `regionResolved` action carries the raw ping
5616
5641
  // samples (one per probe) from `RegionsPingService`; the
5617
5642
  // catalog flattens them to a comma-separated string since
5618
- // `toPhaseParams` only accepts primitives.
5619
- //
5620
- // Adopted connections skip `connectToSignaling` → `getRegion`, so
5621
- // `regionResolved` never fires. We synthesise it from `streamAdopted`
5622
- // using the cached ping result the prefetch reused, so the region
5623
- // phase still appears (with its ping samples). Because this fires at
5624
- // adoption time it also re-anchors `lastMilestonePerf`, which keeps the
5625
- // following signaling / webrtc phases at ~0 for adopted sessions.
5643
+ // `toPhaseParams` only accepts primitives. Adopted connections never
5644
+ // run `getRegion`, but `UnrealEffects.regionTelemetryOnAdopt$` replays
5645
+ // the region waterfall (start → end → resolved) from the cached winner,
5646
+ // so this phase still appears with its ping samples.
5626
5647
  {
5627
5648
  id: 'region-resolved',
5628
- event: merge(this.actions$.pipe(ofType(regionResolved)), this.actions$.pipe(ofType(streamAdopted), map(() => this.cachedRegionResult()))).pipe(take(1), map(({ region, pingsMs }) => ({
5649
+ event: this.actions$.pipe(ofType(regionResolved), take(1), map(({ region, pingsMs }) => ({
5629
5650
  region,
5630
5651
  pingsMs: pingsMs.length ? `${pingsMs.join(', ')} ms` : null,
5631
5652
  }))),
@@ -5786,29 +5807,6 @@ class StreamStatusTelemetryService2 {
5786
5807
  return v4();
5787
5808
  }
5788
5809
  }
5789
- /**
5790
- * Region winner + ping samples cached in `localStorage` by
5791
- * `region-ping-prefetch.js` / `RegionsPingService` (`REGION_PING_CACHE_KEY`).
5792
- * Used to fill the region phase for ADOPTED connections, which never run
5793
- * `getRegion()` and so never dispatch `regionResolved`. Empty when there is
5794
- * no usable cache.
5795
- */
5796
- cachedRegionResult() {
5797
- try {
5798
- const raw = localStorage.getItem(REGION_PING_CACHE_KEY);
5799
- const parsed = raw ? JSON.parse(raw) : null;
5800
- if (parsed && typeof parsed.region_code === 'string') {
5801
- return {
5802
- region: parsed.region_code,
5803
- pingsMs: Array.isArray(parsed.result) ? parsed.result : [],
5804
- };
5805
- }
5806
- }
5807
- catch {
5808
- /* ignore — best-effort */
5809
- }
5810
- return { region: '', pingsMs: [] };
5811
- }
5812
5810
  init() {
5813
5811
  // Start a new session when a real connection attempt BEGINS, not on
5814
5812
  // every `initSignalling` dispatch. `initSignalling` is fired multiple