@3dsource/angular-unreal-module 0.0.123-dev.0 → 0.0.123
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,12 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, ChangeDetectionStrategy, Component, Injectable, DestroyRef, effect, untracked, computed, signal, Pipe, input, ElementRef, viewChild, afterNextRender, HostListener, output, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
|
|
3
3
|
import { SourceButtonComponent, SourceLoadingComponent } from '@3dsource/source-ui-native';
|
|
4
|
-
import { merge, map, scan, timestamp, startWith, pairwise, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, takeUntil as takeUntil$1, shareReplay, from, fromEvent, throwError, defer, retry, tap as tap$1, throttleTime, NEVER, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
4
|
+
import { merge, map, scan, timestamp, startWith, pairwise, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, takeUntil as takeUntil$1, shareReplay, from, fromEvent, throwError, defer, retry, repeat, tap as tap$1, throttleTime, NEVER, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
5
5
|
import { createAction, props, createReducer, on, createFeature, createSelector, Store, provideState } from '@ngrx/store';
|
|
6
6
|
import { concatLatestFrom } from '@ngrx/operators';
|
|
7
7
|
import { Actions, createEffect, ofType, provideEffects } from '@ngrx/effects';
|
|
8
8
|
import { whereNot, Logger, calculateMedian, clampf, lerp, getCanvasCached, getSnapshot, fitIntoRectangle, Truthy, tapLog, Falsy, generateUuid, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, Semaphore, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
|
|
9
|
-
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, takeUntil, catchError, withLatestFrom, exhaustMap, switchMap as switchMap$1, timeout as timeout$1,
|
|
9
|
+
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, takeUntil, catchError, withLatestFrom, exhaustMap, switchMap as switchMap$1, timeout as timeout$1, finalize, first, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
10
10
|
import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
|
|
11
11
|
import { HttpClient } from '@angular/common/http';
|
|
12
12
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
@@ -207,8 +207,31 @@ const CONSOLE_COMMAND_DISABLE_MESSAGES = 'DisableAllScreenMessages';
|
|
|
207
207
|
const CONSOLE_COMMAND_PIXEL_QUALITY = 'PixelStreaming.FreezeFrameQuality 95';
|
|
208
208
|
const FULL_HD_WIDTH = 1920;
|
|
209
209
|
const FULL_HD_HEIGHT = 1080;
|
|
210
|
-
|
|
210
|
+
// Bound on a single WS upgrade attempt (`createWebSocket`). On cold
|
|
211
|
+
// connections the TCP+TLS+WS handshake can legitimately take 3-5 s
|
|
212
|
+
// (cold DNS, TLS session miss). 2 s was too aggressive — first
|
|
213
|
+
// attempts kept timing out and the second attempt (with warm caches)
|
|
214
|
+
// would connect in <500 ms. 10 s gives slow networks room without
|
|
215
|
+
// letting a truly hung handshake hold up `connectToCirrus` forever.
|
|
216
|
+
const WS_TIMEOUT = 6000;
|
|
211
217
|
const POLLING_TIME = 4000;
|
|
218
|
+
// Staged retry policy for `SignallingService.connectToSignaling`:
|
|
219
|
+
// INNER_RETRY_LIMIT = max number of WS+orchestration attempts before
|
|
220
|
+
// the pipeline gives up on the current region and bubbles the error
|
|
221
|
+
// to the outer retry. URL rotation (`urlGen`) advances across these.
|
|
222
|
+
// OUTER_RETRY_DELAY = backoff between region re-picks. Intentionally
|
|
223
|
+
// longer than POLLING_TIME so region discovery is rare relative to
|
|
224
|
+
// WS retries.
|
|
225
|
+
const INNER_RETRY_LIMIT = 3;
|
|
226
|
+
const OUTER_RETRY_DELAY = POLLING_TIME * 2;
|
|
227
|
+
// Extra WS-only retries on the SAME wsUrl (cached AwsInstance) BEFORE
|
|
228
|
+
// falling through to a URL-rotating retry that re-polls orchestration.
|
|
229
|
+
// Why: a fresh `default` poll asks orchestrator to RESERVE a new
|
|
230
|
+
// instance — the previous reservation is orphaned (server-side leak).
|
|
231
|
+
// One quick same-wsUrl retry catches transient WS failures (network
|
|
232
|
+
// blip, brief unavailability) without burning a second reservation.
|
|
233
|
+
// Total WS attempts before URL rotation = 1 + WS_RETRY_PER_INSTANCE.
|
|
234
|
+
const WS_RETRY_PER_INSTANCE = 1;
|
|
212
235
|
const WS_OPEN_STATE = 1;
|
|
213
236
|
const DEFAULT_AFK_TIMEOUT_PERIOD = 15;
|
|
214
237
|
const DEFAULT_AFK_TIMEOUT = 600; // 10 minutes
|
|
@@ -1672,9 +1695,9 @@ class AFKService extends SubService {
|
|
|
1672
1695
|
resetAfkWarningTimer() {
|
|
1673
1696
|
if (this.active) {
|
|
1674
1697
|
this.clearTimers();
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1698
|
+
// `selectWarnTimeout` already falls back to DEFAULT_AFK_TIMEOUT when
|
|
1699
|
+
// config is missing, so warnTimeout() always returns a positive number.
|
|
1700
|
+
this.warnTimer = setTimeout(() => this.showAfkOverlay(), this.warnTimeout() * 1000);
|
|
1678
1701
|
}
|
|
1679
1702
|
}
|
|
1680
1703
|
/**
|
|
@@ -1694,7 +1717,8 @@ class AFKService extends SubService {
|
|
|
1694
1717
|
}
|
|
1695
1718
|
reset() {
|
|
1696
1719
|
this.hideOverlay();
|
|
1697
|
-
|
|
1720
|
+
// startAfkWarningTimer → resetAfkWarningTimer → clearTimers() already
|
|
1721
|
+
// clears the countdown interval, no separate clearInterval needed.
|
|
1698
1722
|
this.startAfkWarningTimer();
|
|
1699
1723
|
}
|
|
1700
1724
|
showAfkOverlay() {
|
|
@@ -2202,11 +2226,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2202
2226
|
type: Injectable
|
|
2203
2227
|
}], ctorParameters: () => [] });
|
|
2204
2228
|
|
|
2205
|
-
class CommandTelemetryService {
|
|
2229
|
+
class CommandTelemetryService extends SubService {
|
|
2206
2230
|
constructor() {
|
|
2207
|
-
|
|
2208
|
-
this.destroyRef = inject(DestroyRef);
|
|
2209
|
-
this.destroy$ = new Subject();
|
|
2231
|
+
super();
|
|
2210
2232
|
this.appId = 'metabox-telemetry';
|
|
2211
2233
|
this.commandsSent = {};
|
|
2212
2234
|
this.exileTimeout = 60000;
|
|
@@ -2214,13 +2236,7 @@ class CommandTelemetryService {
|
|
|
2214
2236
|
this.commandSequenceNumber = 0;
|
|
2215
2237
|
this.payloads = [];
|
|
2216
2238
|
this.httpClient = inject(HttpClient);
|
|
2217
|
-
this.unrealConfig = inject(UNREAL_CONFIG, {
|
|
2218
|
-
optional: true,
|
|
2219
|
-
});
|
|
2220
|
-
this.destroyRef.onDestroy(() => {
|
|
2221
|
-
this.destroy$.next();
|
|
2222
|
-
this.destroy$.complete();
|
|
2223
|
-
});
|
|
2239
|
+
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
2224
2240
|
this.init();
|
|
2225
2241
|
}
|
|
2226
2242
|
init() {
|
|
@@ -2654,32 +2670,35 @@ class SignallingService extends SubService {
|
|
|
2654
2670
|
this.onConfig$ = new Subject();
|
|
2655
2671
|
this.onWebRtcIce$ = new Subject();
|
|
2656
2672
|
this.onWebRtcAnswer$ = new Subject();
|
|
2657
|
-
//
|
|
2658
|
-
//
|
|
2659
|
-
//
|
|
2660
|
-
|
|
2661
|
-
this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection), tap(() => this.close({
|
|
2662
|
-
code: 1000,
|
|
2663
|
-
reason: DisconnectReason.AbortEstablishingConnection,
|
|
2664
|
-
})), share$1());
|
|
2673
|
+
// Pure signal — no side-effects. The `close()` is wired ONCE in init()
|
|
2674
|
+
// below so a single abort event triggers exactly one close, regardless
|
|
2675
|
+
// of how many pipelines have `takeUntil(this.abort$)` attached.
|
|
2676
|
+
this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection));
|
|
2665
2677
|
this.ws = null;
|
|
2666
2678
|
this.wsMsgHandlers = {};
|
|
2667
2679
|
this.init();
|
|
2668
|
-
this.destroyRef.onDestroy(() => this.close({
|
|
2669
|
-
code: 1000,
|
|
2670
|
-
reason: DisconnectReason.AbortEstablishingConnection,
|
|
2671
|
-
}));
|
|
2672
2680
|
}
|
|
2673
2681
|
init() {
|
|
2674
2682
|
if (this.unrealConfig?.playwright) {
|
|
2675
2683
|
return;
|
|
2676
2684
|
}
|
|
2677
2685
|
this.setHandlersFromStream();
|
|
2678
|
-
//
|
|
2679
|
-
//
|
|
2680
|
-
//
|
|
2681
|
-
|
|
2682
|
-
|
|
2686
|
+
// Single owner of the "close on abort" side-effect. Previously this
|
|
2687
|
+
// lived as a `tap` inside `abort$` itself, which made every
|
|
2688
|
+
// `takeUntil(this.abort$)` subscriber fire its own close().
|
|
2689
|
+
this.abort$.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
2690
|
+
this.close({
|
|
2691
|
+
code: 1000,
|
|
2692
|
+
reason: DisconnectReason.AbortEstablishingConnection,
|
|
2693
|
+
});
|
|
2694
|
+
});
|
|
2695
|
+
// `dataChannelReady` is a per-session event. Bind ONLY to destroy$, not
|
|
2696
|
+
// to abort$ — `abort$` fires on every destroyUnrealScene (route swap),
|
|
2697
|
+
// and `init()` runs once in the constructor. If these subs were torn
|
|
2698
|
+
// down by abort$, a second in-injector session (e.g. navigating between
|
|
2699
|
+
// two routes that both host <app-unreal-scene>) would never send
|
|
2700
|
+
// `p2pEstablished` / `streamPreparedForUserInteraction`. `send()` is
|
|
2701
|
+
// already a no-op on a closed WS, so there's no risk of stray messages.
|
|
2683
2702
|
this.actions$
|
|
2684
2703
|
.pipe(ofType(dataChannelReady), takeUntil(this.destroy$))
|
|
2685
2704
|
.subscribe(() => {
|
|
@@ -2709,36 +2728,93 @@ class SignallingService extends SubService {
|
|
|
2709
2728
|
return EMPTY;
|
|
2710
2729
|
}
|
|
2711
2730
|
Logger.log('CONNECT TO SIGNALLING');
|
|
2731
|
+
// Side-effects that belong to one logical `connectToSignaling`
|
|
2732
|
+
// invocation (NOT one retry attempt). Wrapping them in a `defer`
|
|
2733
|
+
// makes them fire on subscribe (lazy), but only ONCE — the retry
|
|
2734
|
+
// below sits INSIDE this defer's projection so re-subscribing the
|
|
2735
|
+
// inner pipeline does not re-trigger these.
|
|
2712
2736
|
return defer(() => {
|
|
2713
|
-
// Preparation for WebSocket Orchestration. Inside defer so these
|
|
2714
|
-
// side effects only fire once the caller actually subscribes.
|
|
2715
2737
|
this.correlationId = generateUuid();
|
|
2716
2738
|
this.commandTelemetryService.reset();
|
|
2717
2739
|
this.startEstablishingConnection();
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2740
|
+
// Staged retry:
|
|
2741
|
+
// * `getRegion()` is pulled in front of the retry — region
|
|
2742
|
+
// discovery happens once per outer cycle.
|
|
2743
|
+
// * `connectAttempt(urls)` owns inner retry + URL rotation +
|
|
2744
|
+
// WS connect. WS/orch failures stay inside it (capped at
|
|
2745
|
+
// `INNER_RETRY_LIMIT`).
|
|
2746
|
+
// * If the inner retry exhausts, the error propagates to the
|
|
2747
|
+
// OUTER retry below which re-runs `getRegion()` (rare, real
|
|
2748
|
+
// "region down" case). Outer delay > inner so escalation is
|
|
2749
|
+
// bounded.
|
|
2750
|
+
const connectCycle$ = this.getRegion().pipe(tap((region) => this.region.set(region ?? '')), map$1((region) => this.adaptUrlsToRegion(urlList, region)), switchMap((urls) => this.connectAttempt(urls)), takeUntil(this.abort$));
|
|
2751
|
+
return connectCycle$.pipe(retry({
|
|
2752
|
+
delay: (err, attempt) => {
|
|
2753
|
+
this.store.dispatch(signalingRetryAttempted({
|
|
2754
|
+
attempt,
|
|
2755
|
+
reason: err?.message ?? 'unknown',
|
|
2756
|
+
kind: 'region-cycle',
|
|
2757
|
+
}));
|
|
2758
|
+
Logger.info(`Outer retry (region re-pick) in ${OUTER_RETRY_DELAY}ms. Attempt# ${attempt}.`, err?.message);
|
|
2759
|
+
return timer(OUTER_RETRY_DELAY);
|
|
2760
|
+
},
|
|
2761
|
+
}));
|
|
2762
|
+
}).pipe(takeUntil(this.abort$));
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* One inner cycle: rotate `urlGen` over the pool, resolve via
|
|
2766
|
+
* {@link getAwsInstance}, open WS via {@link connectToCirrus},
|
|
2767
|
+
* retry up to {@link INNER_RETRY_LIMIT} on any hard failure. The
|
|
2768
|
+
* urlGen lives in this closure so retries advance through the
|
|
2769
|
+
* pool. Exhaustion bubbles to {@link connectToSignaling}'s outer
|
|
2770
|
+
* retry which re-pings the region.
|
|
2771
|
+
*/
|
|
2772
|
+
connectAttempt(urls) {
|
|
2773
|
+
const urlGen = getActiveUrl(urls);
|
|
2774
|
+
return defer(() => this.getAwsInstance(`${urlGen.next().value}`)).pipe(tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl }) =>
|
|
2775
|
+
// ── WS-only retry tier ─────────────────────────────────────────────
|
|
2776
|
+
// Tries the SAME `wsUrl` up to `WS_RETRY_PER_INSTANCE` extra times
|
|
2777
|
+
// before letting the error bubble up to the outer (URL-rotating)
|
|
2778
|
+
// retry. This is important because the outer retry calls
|
|
2779
|
+
// `getAwsInstance(url)` again, which polls orchestration again,
|
|
2780
|
+
// which causes orchestrator to RESERVE A NEW instance — the
|
|
2781
|
+
// previous reservation is orphaned (server-side leak).
|
|
2782
|
+
//
|
|
2783
|
+
// The same-wsUrl retry catches transient WS failures (network
|
|
2784
|
+
// blip, brief region unavailability, slow cold handshake) without
|
|
2785
|
+
// burning a second reservation. If WS still fails after these
|
|
2786
|
+
// extra attempts, the instance is genuinely unusable and we
|
|
2787
|
+
// accept the cost of rotating.
|
|
2788
|
+
this.connectToCirrus(wsUrl ?? '').pipe(filter(Truthy), take(1), retry({
|
|
2789
|
+
count: WS_RETRY_PER_INSTANCE,
|
|
2790
|
+
delay: (err, attempt) => {
|
|
2791
|
+
this.store.dispatch(signalingRetryAttempted({
|
|
2792
|
+
attempt,
|
|
2793
|
+
reason: err?.message ?? 'unknown',
|
|
2794
|
+
kind: 'ws-retry',
|
|
2795
|
+
}));
|
|
2796
|
+
this.showStatusMessage(UnrealStatusMessage.CONNECTING_TO_SESSION);
|
|
2797
|
+
Logger.info(`WS-only retry on SAME url in ${POLLING_TIME}ms. Attempt# ${attempt}.`, err?.message);
|
|
2798
|
+
return timer(POLLING_TIME);
|
|
2799
|
+
},
|
|
2800
|
+
}))),
|
|
2801
|
+
// ── URL-rotating retry tier ─────────────────────────────────────────
|
|
2802
|
+
// Triggers only when the WS-only retries above are exhausted. Each
|
|
2803
|
+
// attempt advances `urlGen` → new `getAwsInstance(url)` → new
|
|
2804
|
+
// orchestration poll → new AwsInstance reservation.
|
|
2725
2805
|
retry({
|
|
2806
|
+
count: INNER_RETRY_LIMIT,
|
|
2726
2807
|
delay: (err, attempt) => {
|
|
2727
2808
|
this.store.dispatch(signalingRetryAttempted({
|
|
2728
2809
|
attempt,
|
|
2729
2810
|
reason: err?.message ?? 'unknown',
|
|
2730
2811
|
kind: 'connect',
|
|
2731
2812
|
}));
|
|
2732
|
-
|
|
2813
|
+
this.showStatusMessage(UnrealStatusMessage.CONNECTING_TO_SESSION);
|
|
2814
|
+
Logger.info(`Inner retry (WS+orch) in ${POLLING_TIME}ms. Attempt# ${attempt}.`, err?.message);
|
|
2733
2815
|
return timer(POLLING_TIME);
|
|
2734
2816
|
},
|
|
2735
|
-
|
|
2736
|
-
}),
|
|
2737
|
-
// abort$ sits OUTSIDE retry, so it kills the entire retry loop,
|
|
2738
|
-
// not just the current attempt. destroy$ is the defense-in-depth
|
|
2739
|
-
// signal for injector teardown — if the abort action chain doesn't
|
|
2740
|
-
// run for any reason, destroy$ still stops the loop.
|
|
2741
|
-
takeUntil(merge(this.abort$, this.destroy$)));
|
|
2817
|
+
}));
|
|
2742
2818
|
}
|
|
2743
2819
|
startEstablishingConnection() {
|
|
2744
2820
|
this.showStatusMessage(UnrealStatusMessage.STARTING_YOUR_SESSION);
|
|
@@ -2753,177 +2829,117 @@ class SignallingService extends SubService {
|
|
|
2753
2829
|
});
|
|
2754
2830
|
}
|
|
2755
2831
|
/**
|
|
2756
|
-
*
|
|
2757
|
-
*
|
|
2758
|
-
*
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
*
|
|
2767
|
-
*
|
|
2768
|
-
*
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
*
|
|
2779
|
-
*
|
|
2780
|
-
*
|
|
2781
|
-
*
|
|
2782
|
-
*
|
|
2783
|
-
* 8. **Mapping to `AwsInstance`** — Converts the validated orchestration payload into:
|
|
2784
|
-
* - `wsUrl`: via `httpUrlToWs(\`\${location.protocol}//\${data.signallingServer}\`)`
|
|
2785
|
-
* - `pollingUrl`: the base `signalingUrl` that succeeded
|
|
2786
|
-
* - `instanceName`: the (validated) `data.signallingServer`
|
|
2787
|
-
* 9. **Retry policy** — On *any* upstream error (timeout, HTTP error, invalid payload),
|
|
2788
|
-
* `retry({ delay })`:
|
|
2789
|
-
* - Shows a “connecting” status,
|
|
2790
|
-
* - Logs the attempt number,
|
|
2791
|
-
* - Waits `WS_TIMEOUT` ms (via `timer`) and **resubscribes**, advancing `urlGen`.
|
|
2792
|
-
*
|
|
2793
|
-
* ### Concurrency semantics
|
|
2794
|
-
*
|
|
2795
|
-
* - `exhaustMap` guarantees a **single** HTTP request per attempt; later
|
|
2796
|
-
* `{clientId, viewId}` emissions are ignored until the current request completes.
|
|
2797
|
-
*
|
|
2798
|
-
* ### Telemetry semantics
|
|
2799
|
-
*
|
|
2800
|
-
* - `trackStartCommand('EXT-getSignaling')` begins before work.
|
|
2801
|
-
* - `trackStopCommand('getSignaling', {...})` runs on receipt of an orchestration response.
|
|
2802
|
-
* If an attempt fails (e.g., timeout), stopping the span is the responsibility of
|
|
2803
|
-
* your telemetry layer or a `finalize` elsewhere if desired.
|
|
2804
|
-
*
|
|
2805
|
-
* ### Error & retry semantics
|
|
2806
|
-
*
|
|
2807
|
-
* - Missing/never-emitted IDs → timeout → retry with next URL.
|
|
2808
|
-
* - HTTP/network error → retry with next URL.
|
|
2809
|
-
* - Invalid orchestration payload (`error` set or empty `signallingServer`) → throw in
|
|
2810
|
-
* validation filter → retry with next URL.
|
|
2811
|
-
* - Short-circuit WS path **does not** retry (emits once, completes).
|
|
2812
|
-
*
|
|
2813
|
-
* @param urlsPool - Ordered list of base URLs to probe. Retries advance through this list
|
|
2814
|
-
* until a working signaling server is found. A candidate may also be a direct `ws://`/`wss://`
|
|
2815
|
-
* URL to short-circuit HTTP orchestration.
|
|
2816
|
-
*
|
|
2817
|
-
* @returns Observable that emits exactly one {@link AwsInstance} on success and then completes.
|
|
2818
|
-
* On failure, the stream errors; the built-in `retry` operator re-subscribes after `WS_TIMEOUT`
|
|
2819
|
-
* and advances to the next URL candidate.
|
|
2820
|
-
*
|
|
2821
|
-
* @throws Emits an error within the observable chain (not a thrown synchronous exception) when:
|
|
2822
|
-
* - The HTTP request fails or times out.
|
|
2823
|
-
* - The orchestration response indicates an error or an empty `signallingServer`.
|
|
2824
|
-
* - Any other operator in the chain surfaces an error.
|
|
2825
|
-
*
|
|
2826
|
-
* @remarks
|
|
2827
|
-
* - The URL generator (`urlGen`) is created **outside** `defer`, so resubscriptions
|
|
2828
|
-
* advance the pool. Creating it inside `defer` would restart from the first URL each retry.
|
|
2829
|
-
* - `location.protocol` is used to derive `ws` vs. `wss`. If this may run in non-browser
|
|
2830
|
-
* contexts (e.g., SSR), guard or abstract this logic.
|
|
2831
|
-
* - Consider capping retries with `count` in `retry({ count, delay })` when appropriate.
|
|
2832
|
-
*
|
|
2833
|
-
* @example
|
|
2834
|
-
* ```ts
|
|
2835
|
-
* getAwsInstance(['https://signaling.example.com/', 'wss://direct.example.com'])
|
|
2836
|
-
* .subscribe({
|
|
2837
|
-
* next: ({ wsUrl }) => connect(wsUrl),
|
|
2838
|
-
* error: (e) => console.error('Unable to establish signaling:', e),
|
|
2839
|
-
* });
|
|
2840
|
-
* ```
|
|
2841
|
-
*
|
|
2842
|
-
* @see httpUrlToWs
|
|
2843
|
-
* @see selectClientAndViewIds
|
|
2844
|
-
* @see setStatusPercentSignallingServer
|
|
2832
|
+
* Resolve one URL to an `AwsInstance`. Dispatches to
|
|
2833
|
+
* {@link directWsInstance} for `ws://` / `wss://` URLs, otherwise
|
|
2834
|
+
* to {@link pollOrchestration}.
|
|
2835
|
+
*/
|
|
2836
|
+
getAwsInstance(url) {
|
|
2837
|
+
return url.match(/^ws(s?):\/\//i)
|
|
2838
|
+
? this.directWsInstance(url)
|
|
2839
|
+
: this.pollOrchestration(url);
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Direct `ws://` / `wss://` short-circuit — no HTTP orchestration.
|
|
2843
|
+
* Waits for `clientId` and `viewId` to be truthy in the store, then
|
|
2844
|
+
* emits a `Direct Connection` `AwsInstance` and completes.
|
|
2845
|
+
*/
|
|
2846
|
+
directWsInstance(url) {
|
|
2847
|
+
return this.store.select(selectClientAndViewIds).pipe(filter(({ clientId, viewId }) => !!clientId && !!viewId), take(1), map$1(() => ({
|
|
2848
|
+
wsUrl: url,
|
|
2849
|
+
pollingUrl: null,
|
|
2850
|
+
instanceName: 'Direct Connection',
|
|
2851
|
+
})));
|
|
2852
|
+
}
|
|
2853
|
+
/**
|
|
2854
|
+
* HTTP-orchestration path. Brackets {@link pollLoop} with the
|
|
2855
|
+
* `pollingStarted`/`pollingEnded` actions and the `EXT-getSignaling`
|
|
2856
|
+
* telemetry span (start in `defer`, stop in `finalize` with the
|
|
2857
|
+
* last response captured in the closure). Maps the final ready
|
|
2858
|
+
* response to an `AwsInstance`.
|
|
2845
2859
|
*/
|
|
2846
|
-
|
|
2847
|
-
this.commandTelemetryService.trackStartCommand('EXT-getSignaling');
|
|
2848
|
-
// Stateful iterator over candidate URLs; retries advance this sequence.
|
|
2849
|
-
const urlGen = getActiveUrl(urlsPool);
|
|
2860
|
+
pollOrchestration(url) {
|
|
2850
2861
|
return defer(() => {
|
|
2851
|
-
|
|
2852
|
-
// Short-circuit if the candidate is already a WS endpoint.
|
|
2853
|
-
if (signalingUrl.match(/^ws(s?):\/\//i)) {
|
|
2854
|
-
return this.store.select(selectClientAndViewIds).pipe(filter(({ clientId, viewId }) => !!clientId && !!viewId), map$1(() => {
|
|
2855
|
-
return {
|
|
2856
|
-
wsUrl: signalingUrl,
|
|
2857
|
-
pollingUrl: null,
|
|
2858
|
-
instanceName: 'Direct Connection',
|
|
2859
|
-
};
|
|
2860
|
-
}));
|
|
2861
|
-
}
|
|
2862
|
-
// Old (HTTP-polling) orchestration flow — mark the round trip so
|
|
2863
|
-
// the signaling-phase timeline can show it as a distinct segment.
|
|
2862
|
+
let lastResp;
|
|
2864
2863
|
this.store.dispatch(pollingStarted());
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
tap(({ clientId, viewId }) => {
|
|
2864
|
+
this.commandTelemetryService.trackStartCommand('EXT-getSignaling');
|
|
2865
|
+
return this.store.select(selectClientAndViewIds).pipe(tap(({ clientId, viewId }) => {
|
|
2868
2866
|
if (!clientId || !viewId) {
|
|
2869
2867
|
Logger.error('Client ID or View ID is not set');
|
|
2870
2868
|
}
|
|
2871
|
-
}),
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
timeout(POLLING_TIME),
|
|
2880
|
-
// Validate response; report progress; fail fast on invalid payloads.
|
|
2881
|
-
filter((data) => {
|
|
2882
|
-
this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...data, multi: true });
|
|
2883
|
-
if (data.signallingServer === '' || data.error) {
|
|
2884
|
-
this.showStatusMessage(data?.mm_message || 'Server not found');
|
|
2885
|
-
if (data?.info) {
|
|
2886
|
-
this.store.dispatch(setStatusPercentSignallingServer({
|
|
2887
|
-
percent: data.info.percent,
|
|
2888
|
-
}));
|
|
2889
|
-
}
|
|
2890
|
-
// Throwing here fails the attempt and activates the retry strategy.
|
|
2891
|
-
throw new Error(`Orchestration message => ${data?.mm_message || 'Polling retry'}`);
|
|
2892
|
-
}
|
|
2893
|
-
return true;
|
|
2894
|
-
}),
|
|
2895
|
-
// Map a valid orchestration payload into the final AwsInstance.
|
|
2896
|
-
map$1((data) => {
|
|
2869
|
+
}), filter(({ clientId, viewId }) => !!clientId && !!viewId), take(1), switchMap(({ clientId, viewId }) => this.pollLoop(`${url}${clientId}/${viewId}`).pipe(tap((data) => {
|
|
2870
|
+
lastResp = data;
|
|
2871
|
+
}))), map$1((data) => ({
|
|
2872
|
+
wsUrl: httpUrlToWs(`${location.protocol}//${data.signallingServer}`),
|
|
2873
|
+
pollingUrl: url,
|
|
2874
|
+
instanceName: data.signallingServer ?? '',
|
|
2875
|
+
})), finalize(() => {
|
|
2876
|
+
this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...(lastResp ?? {}), multi: true });
|
|
2897
2877
|
this.store.dispatch(pollingEnded());
|
|
2898
|
-
const wsUrl = httpUrlToWs(`${location.protocol}//${data.signallingServer}`);
|
|
2899
|
-
return {
|
|
2900
|
-
wsUrl,
|
|
2901
|
-
pollingUrl: signalingUrl,
|
|
2902
|
-
instanceName: data.signallingServer ?? '',
|
|
2903
|
-
};
|
|
2904
2878
|
}));
|
|
2905
|
-
})
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Poll the SAME `url` until ready, or until HTTP 4xx says the URL
|
|
2883
|
+
* is dead.
|
|
2884
|
+
*
|
|
2885
|
+
* | Response | Class | Action |
|
|
2886
|
+
* | ---------------------------------------- | ---------- | ---------------------------- |
|
|
2887
|
+
* | 200 `{ signallingServer: 'cirrus', ... }`| ✅ Ready | `first()` emits, loop ends |
|
|
2888
|
+
* | 200 empty `signallingServer` (any error/info/mm_message) | ⏳ Progress | Dispatch status+percent, re-poll same URL |
|
|
2889
|
+
* | HTTP 4xx (except 429) | ❌ Hard | Re-throw → caller rotates URL |
|
|
2890
|
+
* | HTTP 5xx / 429 / timeout / network | ⚠️ Soft | `EMPTY` → `repeat` same URL |
|
|
2891
|
+
*
|
|
2892
|
+
* `data.error` is NOT treated as fatal — the backend reuses it for
|
|
2893
|
+
* progress strings like `"Ready in ~2m 23s"`. The only fatal exits
|
|
2894
|
+
* are HTTP 4xx and `abort$` upstream.
|
|
2895
|
+
*/
|
|
2896
|
+
pollLoop(url) {
|
|
2897
|
+
return defer(() => this.httpClient.get(url)).pipe(timeout(POLLING_TIME), catchError((err) => {
|
|
2898
|
+
// HTTP 4xx (except 429) = the URL itself is dead → rotate.
|
|
2899
|
+
const status = err?.status;
|
|
2900
|
+
if (typeof status === 'number' &&
|
|
2901
|
+
status >= 400 &&
|
|
2902
|
+
status < 500 &&
|
|
2903
|
+
status !== 429) {
|
|
2904
|
+
Logger.warn(`Polling HARD error on ${url} (HTTP ${status}); rotating URL`);
|
|
2905
|
+
return throwError(() => err);
|
|
2906
|
+
}
|
|
2907
|
+
// Everything else = soft → re-poll same URL.
|
|
2908
|
+
Logger.warn(`Polling soft error on ${url} (${err?.message ?? err}); re-polling same URL`);
|
|
2909
|
+
return EMPTY;
|
|
2910
|
+
}), tap((data) => {
|
|
2911
|
+
// Ready response is handled by `first()` below — nothing to
|
|
2912
|
+
// dispatch here. Empty `signallingServer` = progress.
|
|
2913
|
+
if (data.signallingServer)
|
|
2914
|
+
return;
|
|
2915
|
+
this.showStatusMessage(data?.mm_message || 'Server not found');
|
|
2916
|
+
if (data?.info) {
|
|
2917
|
+
this.store.dispatch(setStatusPercentSignallingServer({
|
|
2918
|
+
percent: data.info.percent,
|
|
2913
2919
|
}));
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
+
}
|
|
2921
|
+
}),
|
|
2922
|
+
// Re-subscribe (= poll again) after each completed response OR
|
|
2923
|
+
// after `catchError` swallowed a transient failure.
|
|
2924
|
+
repeat({ delay: () => timer(POLLING_TIME) }),
|
|
2925
|
+
// Stop the poll loop as soon as we get a ready response.
|
|
2926
|
+
first((data) => !!data.signallingServer));
|
|
2920
2927
|
}
|
|
2921
2928
|
connectToCirrus(wsUrl) {
|
|
2922
|
-
|
|
2923
|
-
|
|
2929
|
+
// Wrapped in `defer` so each subscription (including ws-retry
|
|
2930
|
+
// re-subscriptions from `connectAttempt`) starts a FRESH WS attempt:
|
|
2931
|
+
// the previous socket is closed and a new one is created. Without
|
|
2932
|
+
// this, the WS-only retry would just re-subscribe the SAME WS
|
|
2933
|
+
// observable, which in production-defer'd `createWebSocket` would
|
|
2934
|
+
// still create a new socket, but in tests would re-emit the same
|
|
2935
|
+
// (already-errored) mock observable.
|
|
2936
|
+
return defer(() => {
|
|
2937
|
+
this.close({ code: 1000, reason: DisconnectReason.NewConnectionAttempt });
|
|
2938
|
+
return createWebSocket(wsUrl);
|
|
2939
|
+
}).pipe(takeUntil(this.abort$), tapLog('WS CREATED'), tap((ws) => {
|
|
2924
2940
|
this.commandTelemetryService.trackStartCommand('EXT-iceCandidate');
|
|
2925
2941
|
this.addWsHandlers(ws);
|
|
2926
|
-
})
|
|
2942
|
+
}));
|
|
2927
2943
|
}
|
|
2928
2944
|
addWsHandlers(ws) {
|
|
2929
2945
|
ws.onmessage = OnMessageHandler.bind(this);
|
|
@@ -2942,9 +2958,6 @@ class SignallingService extends SubService {
|
|
|
2942
2958
|
*/
|
|
2943
2959
|
close(data) {
|
|
2944
2960
|
const { code, reason } = data;
|
|
2945
|
-
// Signal "no answer in flight anymore" to any subscriber that observes
|
|
2946
|
-
// raw emissions (today's subscribers filter Truthy, but the Subject's
|
|
2947
|
-
// `| null` type is the contract — keep it honored).
|
|
2948
2961
|
this.onWebRtcAnswer$.next(null);
|
|
2949
2962
|
this.orchestrationRetrySub?.unsubscribe();
|
|
2950
2963
|
if (this.ws) {
|
|
@@ -4046,25 +4059,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4046
4059
|
}]
|
|
4047
4060
|
}] });
|
|
4048
4061
|
|
|
4049
|
-
class UnrealCommunicatorService {
|
|
4062
|
+
class UnrealCommunicatorService extends SubService {
|
|
4050
4063
|
constructor() {
|
|
4051
|
-
|
|
4052
|
-
this.actions$ = inject(Actions);
|
|
4064
|
+
super();
|
|
4053
4065
|
this.commandTelemetryService = inject(CommandTelemetryService);
|
|
4054
4066
|
this.webRtcPlayerService = inject(WebRtcPlayerService);
|
|
4055
4067
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4056
|
-
this.destroyRef = inject(DestroyRef);
|
|
4057
|
-
this.destroy$ = new Subject();
|
|
4058
4068
|
this.cirrusConnected = this.store.selectSignal(unrealFeature.selectCirrusConnected);
|
|
4059
4069
|
this.afkIgnoredCommands = new Set(['executeConsoleCommand']);
|
|
4060
4070
|
this.skipAfkReset = false;
|
|
4061
4071
|
// Throttle: input events fire 60-200x/sec, but AFK timeouts are measured in
|
|
4062
4072
|
// seconds. Leading-edge throttle dispatches once, then suppresses for 1s.
|
|
4063
4073
|
this.resetAfk$ = new Subject();
|
|
4064
|
-
this.destroyRef.onDestroy(() => {
|
|
4065
|
-
this.destroy$.next();
|
|
4066
|
-
this.destroy$.complete();
|
|
4067
|
-
});
|
|
4068
4074
|
this.resetAfk$
|
|
4069
4075
|
.pipe(throttleTime(1000), takeUntil$1(this.destroy$))
|
|
4070
4076
|
.subscribe(() => this.store.dispatch(resetAfk()));
|
|
@@ -4670,20 +4676,21 @@ class RegionsPingService {
|
|
|
4670
4676
|
constructor() {
|
|
4671
4677
|
this.httpClient = inject(HttpClient);
|
|
4672
4678
|
this.store = inject(Store);
|
|
4673
|
-
this.
|
|
4679
|
+
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4674
4680
|
this.destroyRef = inject(DestroyRef);
|
|
4675
|
-
// Aborted
|
|
4676
|
-
//
|
|
4677
|
-
|
|
4681
|
+
// Aborted when the injector is destroyed. Forwarded into every
|
|
4682
|
+
// `startPinging` call's winnerController so in-flight fetches cancel
|
|
4683
|
+
// immediately on route exit instead of running to natural completion.
|
|
4684
|
+
this.destroyController = new AbortController();
|
|
4678
4685
|
this.config = {
|
|
4679
4686
|
ping_count: 2,
|
|
4680
4687
|
ping_timeout: 2000,
|
|
4681
4688
|
max_parallel_requests: 10,
|
|
4682
4689
|
bust_cache: true,
|
|
4683
4690
|
};
|
|
4684
|
-
this.destroyRef.onDestroy(() => this.
|
|
4691
|
+
this.destroyRef.onDestroy(() => this.destroyController.abort());
|
|
4685
4692
|
}
|
|
4686
|
-
getFastest(regionListUrl = this.
|
|
4693
|
+
getFastest(regionListUrl = this.unrealConfig?.regionsPingUrl || '') {
|
|
4687
4694
|
// Self-announce every lifecycle waypoint of the region-discovery
|
|
4688
4695
|
// flow (start / providers-fetched / resolved). The final
|
|
4689
4696
|
// `regionResolved` forwards the winner's full ping-sample array
|
|
@@ -4700,31 +4707,34 @@ class RegionsPingService {
|
|
|
4700
4707
|
const defaultTimeout = providers.timeout ?? this.config.ping_timeout;
|
|
4701
4708
|
return from(this.startPinging(providers.regions, defaultTimeout));
|
|
4702
4709
|
}
|
|
4703
|
-
getProviders(regionListUrl = this.
|
|
4710
|
+
getProviders(regionListUrl = this.unrealConfig?.regionsPingUrl || '') {
|
|
4704
4711
|
return this.httpClient.get(regionListUrl);
|
|
4705
4712
|
}
|
|
4706
4713
|
async startPinging(endpoints, defaultTimeout) {
|
|
4707
|
-
// Two-tier cancellation:
|
|
4708
|
-
// - destroyAbort: service teardown (injector destroy)
|
|
4709
|
-
// - winnerAbort: first successful region resolves Promise.any
|
|
4710
|
-
// Both aborts cascade into every in-flight fetch so losing pings stop
|
|
4711
|
-
// immediately instead of running their full ping_count × timeout budget.
|
|
4712
|
-
const winnerAbort = new AbortController();
|
|
4713
|
-
const stopSignal = AbortSignal.any([
|
|
4714
|
-
winnerAbort.signal,
|
|
4715
|
-
this.destroyAbort.signal,
|
|
4716
|
-
]);
|
|
4717
4714
|
const semaphore = new Semaphore(this.config.max_parallel_requests);
|
|
4715
|
+
// Shared "stop" signal. Aborted when EITHER:
|
|
4716
|
+
// - one region finishes successfully (winner found → cancel losers); OR
|
|
4717
|
+
// - the service's injector is destroyed (route exit → cancel everything).
|
|
4718
|
+
// Without this, losing regions keep pinging to their own `ping_timeout`,
|
|
4719
|
+
// generating up to `(endpoints.length - 1) × ping_count` wasted requests
|
|
4720
|
+
// every time `getFastest` runs, and in-flight fetches outlive the route.
|
|
4721
|
+
const winnerController = new AbortController();
|
|
4722
|
+
const destroySignal = this.destroyController.signal;
|
|
4723
|
+
let onDestroyAbort;
|
|
4724
|
+
if (destroySignal.aborted) {
|
|
4725
|
+
// Service already destroyed — bail every task synchronously.
|
|
4726
|
+
winnerController.abort(destroySignal.reason);
|
|
4727
|
+
}
|
|
4728
|
+
else {
|
|
4729
|
+
onDestroyAbort = () => winnerController.abort(destroySignal.reason);
|
|
4730
|
+
destroySignal.addEventListener('abort', onDestroyAbort, { once: true });
|
|
4731
|
+
}
|
|
4718
4732
|
const tasks = endpoints.map(async ({ region_code, url, timeout }) => {
|
|
4719
4733
|
await semaphore.acquire();
|
|
4720
4734
|
try {
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
}
|
|
4724
|
-
const result = await this.checkLatency(url, timeout ?? defaultTimeout, stopSignal);
|
|
4725
|
-
if (result.length < 2) {
|
|
4735
|
+
const result = await this.checkLatency(url, timeout ?? defaultTimeout, winnerController.signal);
|
|
4736
|
+
if (result.length < 2)
|
|
4726
4737
|
throw new Error('insufficient pings');
|
|
4727
|
-
}
|
|
4728
4738
|
return { region_code, result };
|
|
4729
4739
|
}
|
|
4730
4740
|
finally {
|
|
@@ -4732,18 +4742,31 @@ class RegionsPingService {
|
|
|
4732
4742
|
}
|
|
4733
4743
|
});
|
|
4734
4744
|
try {
|
|
4735
|
-
|
|
4736
|
-
winnerAbort.abort();
|
|
4737
|
-
return winner;
|
|
4745
|
+
return await Promise.any(tasks);
|
|
4738
4746
|
}
|
|
4739
4747
|
catch {
|
|
4740
4748
|
return null;
|
|
4741
4749
|
}
|
|
4750
|
+
finally {
|
|
4751
|
+
// Cancel losing in-flight pings. Safe to call on success (we already
|
|
4752
|
+
// captured the winner) AND on all-failed (no-op, all tasks settled).
|
|
4753
|
+
winnerController.abort();
|
|
4754
|
+
// Detach the destroy listener so it doesn't linger on the long-lived
|
|
4755
|
+
// destroyController.signal across multiple `getFastest` invocations.
|
|
4756
|
+
if (onDestroyAbort) {
|
|
4757
|
+
destroySignal.removeEventListener('abort', onDestroyAbort);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4742
4760
|
}
|
|
4743
|
-
async checkLatency(endpoint, timeout,
|
|
4761
|
+
async checkLatency(endpoint, timeout, abortSignal) {
|
|
4744
4762
|
const pingResults = [];
|
|
4745
4763
|
const sep = endpoint.includes('?') ? '&' : '?';
|
|
4746
4764
|
for (let i = 0; i < this.config.ping_count; i++) {
|
|
4765
|
+
// Bail out as soon as another region's task has won — no point
|
|
4766
|
+
// burning further `ping_timeout` budget here.
|
|
4767
|
+
if (abortSignal?.aborted) {
|
|
4768
|
+
break;
|
|
4769
|
+
}
|
|
4747
4770
|
const startTime = performance.now();
|
|
4748
4771
|
try {
|
|
4749
4772
|
await this.fetchWithTimeout(this.config.bust_cache
|
|
@@ -4757,7 +4780,7 @@ class RegionsPingService {
|
|
|
4757
4780
|
Expires: '0',
|
|
4758
4781
|
},
|
|
4759
4782
|
credentials: 'omit',
|
|
4760
|
-
}, timeout,
|
|
4783
|
+
}, timeout, abortSignal);
|
|
4761
4784
|
}
|
|
4762
4785
|
catch {
|
|
4763
4786
|
// intentional: finally pushes the elapsed time as the latency sample
|
|
@@ -4769,20 +4792,31 @@ class RegionsPingService {
|
|
|
4769
4792
|
}
|
|
4770
4793
|
return pingResults;
|
|
4771
4794
|
}
|
|
4772
|
-
async fetchWithTimeout(url, options = {}, timeout,
|
|
4795
|
+
async fetchWithTimeout(url, options = {}, timeout, abortSignal) {
|
|
4773
4796
|
const controller = new AbortController();
|
|
4774
|
-
options.signal =
|
|
4775
|
-
|
|
4776
|
-
|
|
4797
|
+
options.signal = controller.signal;
|
|
4798
|
+
// Forward an external abort into our local controller so a winner-found signal cancels the in-flight `fetch` immediately. If the
|
|
4799
|
+
// external signal is already aborted, abort synchronously before
|
|
4800
|
+
// even starting the fetch.
|
|
4801
|
+
let onExternalAbort;
|
|
4802
|
+
if (abortSignal) {
|
|
4803
|
+
if (abortSignal.aborted) {
|
|
4804
|
+
controller.abort(abortSignal.reason);
|
|
4805
|
+
}
|
|
4806
|
+
else {
|
|
4807
|
+
onExternalAbort = () => controller.abort(abortSignal.reason);
|
|
4808
|
+
abortSignal.addEventListener('abort', onExternalAbort, { once: true });
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4777
4811
|
const timeoutId = setTimeout(() => controller.abort(new DOMException('Request timed out', 'TimeoutError')), timeout);
|
|
4778
4812
|
try {
|
|
4779
|
-
|
|
4780
|
-
clearTimeout(timeoutId);
|
|
4781
|
-
return response;
|
|
4813
|
+
return await fetch(url, options);
|
|
4782
4814
|
}
|
|
4783
|
-
|
|
4815
|
+
finally {
|
|
4784
4816
|
clearTimeout(timeoutId);
|
|
4785
|
-
|
|
4817
|
+
if (abortSignal && onExternalAbort) {
|
|
4818
|
+
abortSignal.removeEventListener('abort', onExternalAbort);
|
|
4819
|
+
}
|
|
4786
4820
|
}
|
|
4787
4821
|
}
|
|
4788
4822
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -7085,5 +7119,5 @@ function provideUnrealModule() {
|
|
|
7085
7119
|
* Generated bundle index. Do not edit.
|
|
7086
7120
|
*/
|
|
7087
7121
|
|
|
7088
|
-
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
7122
|
+
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
7089
7123
|
//# sourceMappingURL=3dsource-angular-unreal-module.mjs.map
|