@3dsource/angular-unreal-module 0.0.161 → 0.0.163
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/README.md
CHANGED
|
@@ -114,6 +114,15 @@ The `loadChildren()` boundary keeps the streaming engine out of the initial
|
|
|
114
114
|
bundle. Route-scoping `provideUnrealModule()` tears down its effects when the
|
|
115
115
|
route is left.
|
|
116
116
|
|
|
117
|
+
> `UNREAL_CONFIG` on a route's `providers` does **not** work: the engine
|
|
118
|
+
> services are `providedIn: 'root'`, so they are created by the root injector
|
|
119
|
+
> and read the token from there. A route-level value is invisible to them and
|
|
120
|
+
> `inject(UNREAL_CONFIG, { optional: true })` resolves to `null` — e.g. region
|
|
121
|
+
> pinging is skipped entirely (empty `regionsPingUrl`), the orchestration
|
|
122
|
+
> `requestStream` goes out without a region and the post-connection re-ping
|
|
123
|
+
> never runs. Importing only the token at the root does not pull the module into
|
|
124
|
+
> the initial bundle (the package is `sideEffects: false`).
|
|
125
|
+
|
|
117
126
|
### 3. Render the scene
|
|
118
127
|
|
|
119
128
|
```typescript
|
|
@@ -174,6 +183,9 @@ The package publishes two dependency-free scripts for use in the document
|
|
|
174
183
|
`stream-prefetch.js` runs by default only on
|
|
175
184
|
`metabox-configurator/{modular|basic}/...` routes. It reads the same-origin
|
|
176
185
|
`assets/config.json`; override that path with `data-config-url` when needed.
|
|
186
|
+
It also forwards the orchestration-issued `streamRequestId` from the polling
|
|
187
|
+
response to Cirrus on the WebSocket URL (the session `connectionId`) and parks it
|
|
188
|
+
for the Angular side to adopt.
|
|
177
189
|
Pin an exact package version in production when deterministic CDN assets are
|
|
178
190
|
required.
|
|
179
191
|
|
|
@@ -479,7 +479,7 @@ const initialState = {
|
|
|
479
479
|
};
|
|
480
480
|
const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScene, (state, { isVideoPlaying }) => {
|
|
481
481
|
return { ...state, isVideoPlaying };
|
|
482
|
-
}), on(setAwsInstance, (state, { instanceName, wsUrl, pollingUrl }) => {
|
|
482
|
+
}), on(setAwsInstance, (state, { instanceName, wsUrl, pollingUrl, streamRequestId }) => {
|
|
483
483
|
return {
|
|
484
484
|
...state,
|
|
485
485
|
awsInstance: {
|
|
@@ -487,6 +487,7 @@ const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScen
|
|
|
487
487
|
instanceName,
|
|
488
488
|
wsUrl,
|
|
489
489
|
pollingUrl,
|
|
490
|
+
streamRequestId,
|
|
490
491
|
progressComplete: 0,
|
|
491
492
|
},
|
|
492
493
|
};
|
|
@@ -642,7 +643,7 @@ const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScen
|
|
|
642
643
|
return { ...state, analyticsEvent: event };
|
|
643
644
|
}), on(setCirrusConnected, (state) => {
|
|
644
645
|
return { ...state, cirrusConnected: true };
|
|
645
|
-
}), on(streamAdopted, (state, { machineId, ssData }) => {
|
|
646
|
+
}), on(streamAdopted, (state, { machineId, ssData, streamRequestId }) => {
|
|
646
647
|
return {
|
|
647
648
|
...state,
|
|
648
649
|
cirrusConnected: true,
|
|
@@ -653,6 +654,7 @@ const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScen
|
|
|
653
654
|
wsUrl: machineId,
|
|
654
655
|
pollingUrl: null,
|
|
655
656
|
instanceName: 'Adopted Connection',
|
|
657
|
+
streamRequestId: streamRequestId ?? state.awsInstance.streamRequestId,
|
|
656
658
|
progressComplete: 0,
|
|
657
659
|
},
|
|
658
660
|
};
|
|
@@ -782,8 +784,9 @@ function InstanceReadyHandler(msg) {
|
|
|
782
784
|
this.store.dispatch(instanceReady(payload));
|
|
783
785
|
// Open the SECOND socket directly to the stream machine and route the whole
|
|
784
786
|
// WebRTC signalling through it. The orchestration socket stays open for
|
|
785
|
-
// control/lifecycle.
|
|
786
|
-
|
|
787
|
+
// control/lifecycle. `streamRequestId` is handed over so the stream machine's
|
|
788
|
+
// Cirrus reuses the orchestration-issued connection id.
|
|
789
|
+
this.connectToStreamMachine(payload.originInstanceUri, payload.streamRequestId);
|
|
787
790
|
}
|
|
788
791
|
|
|
789
792
|
function InstanceReservedHandler(msg) {
|
|
@@ -1223,8 +1226,19 @@ const selectTotalProgress = createSelector(unrealFeature.selectStatusPercentSign
|
|
|
1223
1226
|
const progressRest = lerp(0, 1 - splitPoint, commandProgress);
|
|
1224
1227
|
return clampf(0, 1, progressSignaling + progressRest);
|
|
1225
1228
|
});
|
|
1226
|
-
/**
|
|
1227
|
-
|
|
1229
|
+
/**
|
|
1230
|
+
* The connection id of the current stream session.
|
|
1231
|
+
*
|
|
1232
|
+
* Primary source is the orchestration, which issues it as `streamRequestId` —
|
|
1233
|
+
* in the polling response (legacy flow) or in `instanceReserved`/`instanceReady`
|
|
1234
|
+
* (new orchestration) — and the browser hands it to Cirrus on the WS upgrade
|
|
1235
|
+
* URL. `ssData.connectionId` (the `ssInfo` message) is the fallback for
|
|
1236
|
+
* backends that don't issue one yet: Cirrus then generates it itself and
|
|
1237
|
+
* reports it back here.
|
|
1238
|
+
*
|
|
1239
|
+
* Used by external consumers (e.g., metabox). Do not remove.
|
|
1240
|
+
*/
|
|
1241
|
+
const selectCirrusConnectionId = createSelector(unrealFeature.selectAwsInstance, unrealFeature.selectSsData, (awsInstance, ssData) => awsInstance?.streamRequestId ?? ssData?.connectionId);
|
|
1228
1242
|
/** Used by external consumers (e.g., metabox). Do not remove. */
|
|
1229
1243
|
const selectUserInactivityDetected = createSelector(unrealFeature.selectAfkCountdown, unrealFeature.selectIsAfkTimerVisible, (remainingSecondsToDisconnect, countdownVisible) => ({
|
|
1230
1244
|
remainingSecondsToDisconnect,
|
|
@@ -1538,6 +1552,16 @@ class RegionsPingService {
|
|
|
1538
1552
|
pingsMs: data.result,
|
|
1539
1553
|
}))), map$1((data) => data.region_code));
|
|
1540
1554
|
}
|
|
1555
|
+
// No regions URL and no cache: there is nothing to ping. Bail out loudly
|
|
1556
|
+
// instead of letting `HttpClient.get('')` request the current document
|
|
1557
|
+
// (which yields HTML, fails to parse and silently resolves an empty
|
|
1558
|
+
// region). The usual cause is `UNREAL_CONFIG` provided on a lazy route
|
|
1559
|
+
// rather than at the application root — this service is
|
|
1560
|
+
// `providedIn: 'root'`, so a route-level value never reaches it.
|
|
1561
|
+
if (!regionListUrl) {
|
|
1562
|
+
Logger.warn('RegionsPingService: regionsPingUrl is empty — skipping region discovery. Provide UNREAL_CONFIG at the APPLICATION ROOT (a route-level provider is invisible to root-scoped services).');
|
|
1563
|
+
return of(undefined).pipe(tap(() => this.store.dispatch(regionPingFetchEnd())), tap(() => this.store.dispatch(regionResolved({ region: '', pingsMs: [] }))));
|
|
1564
|
+
}
|
|
1541
1565
|
return this.getProviders(regionListUrl).pipe(tap(() => this.store.dispatch(regionPingFetchEnd())), switchMap((providers) => this.getPingResult(providers)), catchError(() => of(null)), tapLog('PingResult'),
|
|
1542
1566
|
// Warm the cache with the live result so the next connection (and any
|
|
1543
1567
|
// reload within the TTL) can skip pinging even if the prefetch script
|
|
@@ -1558,9 +1582,11 @@ class RegionsPingService {
|
|
|
1558
1582
|
* All access is guarded — `localStorage` can throw (private mode, disabled
|
|
1559
1583
|
* storage) or be absent (non-browser).
|
|
1560
1584
|
*
|
|
1561
|
-
* Public so adopted connections (which skip `getFastest`) can
|
|
1585
|
+
* Public so adopted connections (which skip `getFastest`) can still report a
|
|
1562
1586
|
* region-resolving telemetry phase from the same cached winner — see
|
|
1563
|
-
* `
|
|
1587
|
+
* `StreamStatusTelemetryService2.emitAdoptedConnection`. Returns `null` when
|
|
1588
|
+
* the region-ping prefetch never ran or its cache expired; that phase then
|
|
1589
|
+
* carries no region and no ping samples.
|
|
1564
1590
|
*/
|
|
1565
1591
|
readCachedWinner() {
|
|
1566
1592
|
try {
|
|
@@ -1606,6 +1632,10 @@ class RegionsPingService {
|
|
|
1606
1632
|
* connection handshake. Aborts with the injector via `destroyController`.
|
|
1607
1633
|
*/
|
|
1608
1634
|
refreshCacheInBackground(regionListUrl = this.unrealConfig?.regionsPingUrl || '') {
|
|
1635
|
+
if (!regionListUrl) {
|
|
1636
|
+
Logger.warn('RegionsPingService: regionsPingUrl is empty — skipping the background region re-ping.');
|
|
1637
|
+
return of(null);
|
|
1638
|
+
}
|
|
1609
1639
|
return defer(() => this.getProviders(regionListUrl)).pipe(switchMap((providers) => from(this.measureAccurate(providers.regions, providers.timeout ?? this.config.ping_timeout))), tap((winner) => {
|
|
1610
1640
|
if (winner) {
|
|
1611
1641
|
this.writeCachedWinner(winner.region_code, winner.result);
|
|
@@ -1819,6 +1849,27 @@ function httpUrlToWs(url) {
|
|
|
1819
1849
|
return url.replace('http://', 'ws://').replace('https://', 'wss://');
|
|
1820
1850
|
}
|
|
1821
1851
|
|
|
1852
|
+
/**
|
|
1853
|
+
* Append the orchestration-issued connection id to a signalling WS URL as
|
|
1854
|
+
* `?streamRequestId=…`.
|
|
1855
|
+
*
|
|
1856
|
+
* Cirrus reads it in its `onConnection` upgrade handler and uses it as the
|
|
1857
|
+
* player's `connectionId` instead of generating one (both the legacy Cirrus and
|
|
1858
|
+
* the new Cirrus-WS keep their `guid()` generator as a fallback). A missing id
|
|
1859
|
+
* is a no-op, so the URL stays byte-identical for backends that don't issue one.
|
|
1860
|
+
*
|
|
1861
|
+
* Only used at the moment of connecting — the URL kept in the store stays clean
|
|
1862
|
+
* (it is rendered in the debug overlay and pattern-matched by
|
|
1863
|
+
* `selectTotalProgress`).
|
|
1864
|
+
*/
|
|
1865
|
+
function withStreamRequestId(wsUrl, streamRequestId) {
|
|
1866
|
+
if (!wsUrl || !streamRequestId) {
|
|
1867
|
+
return wsUrl;
|
|
1868
|
+
}
|
|
1869
|
+
const separator = wsUrl.includes('?') ? '&' : '?';
|
|
1870
|
+
return `${wsUrl}${separator}streamRequestId=${encodeURIComponent(streamRequestId)}`;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1822
1873
|
/**
|
|
1823
1874
|
* Factory that creates a WebSocket connection and wraps it in an RxJS Observable.
|
|
1824
1875
|
*
|
|
@@ -2153,7 +2204,7 @@ class SignallingService extends SubService {
|
|
|
2153
2204
|
*/
|
|
2154
2205
|
connectAttempt(urls) {
|
|
2155
2206
|
const urlGen = getActiveUrl(urls);
|
|
2156
|
-
return defer(() => this.getAwsInstance(`${urlGen.next().value}`)).pipe(tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl }) =>
|
|
2207
|
+
return defer(() => this.getAwsInstance(`${urlGen.next().value}`)).pipe(tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl, streamRequestId }) =>
|
|
2157
2208
|
// ── WS-only retry tier ─────────────────────────────────────────────
|
|
2158
2209
|
// Tries the SAME `wsUrl` up to `WS_RETRY_PER_INSTANCE` extra times
|
|
2159
2210
|
// before letting the error bubble up to the outer (URL-rotating)
|
|
@@ -2167,7 +2218,14 @@ class SignallingService extends SubService {
|
|
|
2167
2218
|
// burning a second reservation. If WS still fails after these
|
|
2168
2219
|
// extra attempts, the instance is genuinely unusable and we
|
|
2169
2220
|
// accept the cost of rotating.
|
|
2170
|
-
|
|
2221
|
+
// POLLING flow only: the polled `signallingServer` IS Cirrus (single
|
|
2222
|
+
// socket), so the orchestration-issued connection id rides on this
|
|
2223
|
+
// upgrade URL. In the new WS flow this socket is orchestration-only
|
|
2224
|
+
// control and `streamRequestId` is still unknown here (it arrives later
|
|
2225
|
+
// in `instanceReserved`/`instanceReady`) → no-op, URL unchanged; the id
|
|
2226
|
+
// is attached to the machine address instead, see
|
|
2227
|
+
// `connectToStreamMachine`.
|
|
2228
|
+
this.connectToCirrus(withStreamRequestId(wsUrl ?? '', streamRequestId)).pipe(filter(Truthy), take(1), retry({
|
|
2171
2229
|
count: WS_RETRY_PER_INSTANCE,
|
|
2172
2230
|
delay: (err, attempt) => {
|
|
2173
2231
|
this.store.dispatch(signalingRetryAttempted({
|
|
@@ -2198,9 +2256,18 @@ class SignallingService extends SubService {
|
|
|
2198
2256
|
},
|
|
2199
2257
|
}));
|
|
2200
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Status message for the connect branch. It does NOT dispatch
|
|
2261
|
+
* `setEstablishingConnection` — `UnrealEffects.connectToSignaling$` already
|
|
2262
|
+
* emits that once per logical attempt via `startWith(...)`, covering BOTH
|
|
2263
|
+
* branches (prefetch adoption and a fresh connect). Dispatching it here as
|
|
2264
|
+
* well fired the boundary twice on every cold start, and since
|
|
2265
|
+
* `StreamStatusTelemetryService2` starts a session on that boundary, the
|
|
2266
|
+
* first session was killed 2 ms in as `restart` — losing the only honest
|
|
2267
|
+
* page-load measurement and the prefetch adoption with it.
|
|
2268
|
+
*/
|
|
2201
2269
|
startEstablishingConnection() {
|
|
2202
2270
|
this.showStatusMessage(UnrealStatusMessage.STARTING_YOUR_SESSION);
|
|
2203
|
-
this.store.dispatch(setEstablishingConnection({ value: true }));
|
|
2204
2271
|
}
|
|
2205
2272
|
adaptUrlsToRegion(urlList, region) {
|
|
2206
2273
|
return urlList.map((url) => {
|
|
@@ -2254,6 +2321,9 @@ class SignallingService extends SubService {
|
|
|
2254
2321
|
wsUrl: httpUrlToWs(`${location.protocol}//${data.signallingServer}`),
|
|
2255
2322
|
pollingUrl: url,
|
|
2256
2323
|
instanceName: data.signallingServer ?? '',
|
|
2324
|
+
// The orchestration issues the connection id here; it is handed to
|
|
2325
|
+
// Cirrus on the WS upgrade URL (see `connectAttempt`).
|
|
2326
|
+
streamRequestId: data.streamRequestId,
|
|
2257
2327
|
})), finalize(() => {
|
|
2258
2328
|
this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...(lastResp ?? {}), multi: true });
|
|
2259
2329
|
this.store.dispatch(pollingEnded());
|
|
@@ -2371,8 +2441,15 @@ class SignallingService extends SubService {
|
|
|
2371
2441
|
*
|
|
2372
2442
|
* "Just connect" — nothing is sent on open (the machine is already reserved
|
|
2373
2443
|
* for this session): Cirrus pushes `config` and Unreal sends the `offer`.
|
|
2444
|
+
*
|
|
2445
|
+
* The orchestration-issued `streamRequestId` (from the same `instanceReady`
|
|
2446
|
+
* payload) rides on this upgrade URL: in the new flow THIS is the Cirrus
|
|
2447
|
+
* socket, so it is the one that must carry the id (the first socket is
|
|
2448
|
+
* orchestration-only control and needs nothing). Without it the stream
|
|
2449
|
+
* machine's Cirrus falls back to its own `guid()` and the session can no
|
|
2450
|
+
* longer be correlated. Absent id → URL unchanged.
|
|
2374
2451
|
*/
|
|
2375
|
-
connectToStreamMachine(originInstanceUri) {
|
|
2452
|
+
connectToStreamMachine(originInstanceUri, streamRequestId) {
|
|
2376
2453
|
if (!originInstanceUri) {
|
|
2377
2454
|
Logger.warn('instanceReady without originInstanceUri — stream socket not opened');
|
|
2378
2455
|
return;
|
|
@@ -2387,7 +2464,7 @@ class SignallingService extends SubService {
|
|
|
2387
2464
|
// before a fresh attempt — e.g. a retry that resolved to a different machine
|
|
2388
2465
|
// or two instanceReady messages before the first connect resolves.
|
|
2389
2466
|
this.teardownStreamWs();
|
|
2390
|
-
this.streamWsSub = createWebSocket(originInstanceUri)
|
|
2467
|
+
this.streamWsSub = createWebSocket(withStreamRequestId(originInstanceUri, streamRequestId))
|
|
2391
2468
|
.pipe(takeUntil(this.abort$), takeUntil(this.destroy$), take(1))
|
|
2392
2469
|
.subscribe({
|
|
2393
2470
|
next: (ws) => {
|
|
@@ -3688,11 +3765,12 @@ class StreamAdoptionService {
|
|
|
3688
3765
|
*
|
|
3689
3766
|
* Wrapped in `defer` so the `tryAdopt()` side effect (which dispatches
|
|
3690
3767
|
* `streamAdopted`) runs at SUBSCRIPTION time, not when the observable is
|
|
3691
|
-
* built. The effect prepends `startWith(setEstablishingConnection(true))
|
|
3692
|
-
*
|
|
3693
|
-
*
|
|
3694
|
-
* `
|
|
3695
|
-
* exist yet and is lost. `defer`
|
|
3768
|
+
* built. The effect prepends `startWith(setEstablishingConnection(true))` —
|
|
3769
|
+
* the single dispatch site for that boundary — which starts the telemetry
|
|
3770
|
+
* session and subscribes its phase catalog; that must happen BEFORE
|
|
3771
|
+
* `streamAdopted`, or `emitAdoptedConnection` fires into a session that does
|
|
3772
|
+
* not exist yet and the whole connection waterfall is lost. `defer`
|
|
3773
|
+
* guarantees that ordering.
|
|
3696
3774
|
*/
|
|
3697
3775
|
awaitAndAdopt$() {
|
|
3698
3776
|
return defer(() => {
|
|
@@ -3762,6 +3840,7 @@ class StreamAdoptionService {
|
|
|
3762
3840
|
this.store.dispatch(streamAdopted({
|
|
3763
3841
|
machineId: parked.machineId ?? null,
|
|
3764
3842
|
ssData: parked.ssData ?? null,
|
|
3843
|
+
streamRequestId: parked.ids?.streamRequestId || null,
|
|
3765
3844
|
timings: parked.timings ?? null,
|
|
3766
3845
|
}));
|
|
3767
3846
|
// Hand the live socket over without re-sending `requestStream`, reusing the
|
|
@@ -4063,9 +4142,12 @@ class UnrealEffects {
|
|
|
4063
4142
|
return this.actions$.pipe(ofType(setAwsInstance),
|
|
4064
4143
|
// require wsUrl present on the action payload
|
|
4065
4144
|
filter((action) => !!action.wsUrl),
|
|
4066
|
-
// for each awsInstance action capture one
|
|
4145
|
+
// for each awsInstance action capture one connectionId snapshot, then wait
|
|
4146
|
+
// for dataChannelConnected. The gate stays on `ssInfo` (unchanged timing);
|
|
4147
|
+
// the id itself now comes from the orchestration when it issued one, and
|
|
4148
|
+
// falls back to the `ssInfo` value otherwise.
|
|
4067
4149
|
switchMap((awsInstance) => {
|
|
4068
|
-
return this.actions$.pipe(ofType(updateCirrusInfo), take(1), switchMap((
|
|
4150
|
+
return this.actions$.pipe(ofType(updateCirrusInfo), take(1), withLatestFrom(this.store.select(selectCirrusConnectionId)), switchMap(([, connectionId]) => {
|
|
4069
4151
|
const abort$ = this.actions$.pipe(ofType(setCirrusDisconnected, // ← "Cirrus closed" action — direct,
|
|
4070
4152
|
// no fan-in tick delay
|
|
4071
4153
|
abortEstablishingConnection), take(1));
|
|
@@ -4082,7 +4164,7 @@ class UnrealEffects {
|
|
|
4082
4164
|
Logger.error(error);
|
|
4083
4165
|
const body = {
|
|
4084
4166
|
awsInstance,
|
|
4085
|
-
connectionId
|
|
4167
|
+
connectionId,
|
|
4086
4168
|
href: location.href,
|
|
4087
4169
|
userAgent: navigator.userAgent,
|
|
4088
4170
|
error,
|
|
@@ -5880,10 +5962,14 @@ class StreamStatusTelemetryService2 {
|
|
|
5880
5962
|
// CLOSER — the `regionResolved` action carries the raw ping
|
|
5881
5963
|
// samples (one per probe) from `RegionsPingService`; the
|
|
5882
5964
|
// catalog flattens them to a comma-separated string since
|
|
5883
|
-
// `toPhaseParams` only accepts primitives.
|
|
5884
|
-
//
|
|
5885
|
-
//
|
|
5886
|
-
//
|
|
5965
|
+
// `toPhaseParams` only accepts primitives.
|
|
5966
|
+
//
|
|
5967
|
+
// Adopted connections never run `getRegion`, so this closer never
|
|
5968
|
+
// fires for them (and is suppressed anyway via PREFETCHED_PHASE_IDS).
|
|
5969
|
+
// Their region phase comes from {@link emitAdoptedConnection}, which
|
|
5970
|
+
// reports `duration: 0`, no milestones, and reads the winner + samples
|
|
5971
|
+
// out of the ping cache — so it is EMPTY when the region-ping prefetch
|
|
5972
|
+
// never ran or its cache expired.
|
|
5887
5973
|
{
|
|
5888
5974
|
id: 'region-resolved',
|
|
5889
5975
|
event: this.actions$.pipe(ofType(regionResolved), take(1), map(({ region, pingsMs }) => ({
|
|
@@ -6053,12 +6139,17 @@ class StreamStatusTelemetryService2 {
|
|
|
6053
6139
|
// times per page load (the unreal-scene effect, `startStream`, the
|
|
6054
6140
|
// reconnect effect), but the connection layer collapses them into one
|
|
6055
6141
|
// attempt via `connectToSignaling$` (`exhaustMap` + `!cirrusConnected`
|
|
6056
|
-
// filter). `setEstablishingConnection(true)` is dispatched exactly
|
|
6057
|
-
//
|
|
6058
|
-
// downstream of
|
|
6059
|
-
//
|
|
6142
|
+
// filter). `setEstablishingConnection(true)` is dispatched exactly once
|
|
6143
|
+
// per real attempt, by that effect's `startWith(...)` — the SINGLE
|
|
6144
|
+
// dispatch site, downstream of the guard and ahead of both branches
|
|
6145
|
+
// (prefetch adoption / fresh connect). So binding here yields one session
|
|
6146
|
+
// per attempt instead of a burst of `restart` churn sessions. Genuine
|
|
6060
6147
|
// reconnects (e.g. DataChannelTimeout) re-enter the same path and
|
|
6061
6148
|
// correctly end the prior session as `restart` + start a new one.
|
|
6149
|
+
//
|
|
6150
|
+
// Do NOT add a second dispatch site: this binding turns every extra
|
|
6151
|
+
// `true` into `endSession('restart')` + a new session, which throws away
|
|
6152
|
+
// the first session's page-load measurement and its prefetch adoption.
|
|
6062
6153
|
this.actions$
|
|
6063
6154
|
.pipe(ofType(setEstablishingConnection), filter$1(({ value }) => value === true), takeUntil$1(this.destroy$))
|
|
6064
6155
|
.subscribe(() => this.startSession());
|