@3dsource/angular-unreal-module 0.0.162 → 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
@@ -183,6 +183,9 @@ The package publishes two dependency-free scripts for use in the document
183
183
  `stream-prefetch.js` runs by default only on
184
184
  `metabox-configurator/{modular|basic}/...` routes. It reads the same-origin
185
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.
186
189
  Pin an exact package version in production when deterministic CDN assets are
187
190
  required.
188
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
- this.connectToStreamMachine(payload.originInstanceUri);
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
- /** Used by external consumers (e.g., metabox). Do not remove. */
1227
- const selectCirrusConnectionId = createSelector(unrealFeature.selectSsData, (data) => data?.connectionId);
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,
@@ -1835,6 +1849,27 @@ function httpUrlToWs(url) {
1835
1849
  return url.replace('http://', 'ws://').replace('https://', 'wss://');
1836
1850
  }
1837
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
+
1838
1873
  /**
1839
1874
  * Factory that creates a WebSocket connection and wraps it in an RxJS Observable.
1840
1875
  *
@@ -2169,7 +2204,7 @@ class SignallingService extends SubService {
2169
2204
  */
2170
2205
  connectAttempt(urls) {
2171
2206
  const urlGen = getActiveUrl(urls);
2172
- 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 }) =>
2173
2208
  // ── WS-only retry tier ─────────────────────────────────────────────
2174
2209
  // Tries the SAME `wsUrl` up to `WS_RETRY_PER_INSTANCE` extra times
2175
2210
  // before letting the error bubble up to the outer (URL-rotating)
@@ -2183,7 +2218,14 @@ class SignallingService extends SubService {
2183
2218
  // burning a second reservation. If WS still fails after these
2184
2219
  // extra attempts, the instance is genuinely unusable and we
2185
2220
  // accept the cost of rotating.
2186
- this.connectToCirrus(wsUrl ?? '').pipe(filter(Truthy), take(1), retry({
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({
2187
2229
  count: WS_RETRY_PER_INSTANCE,
2188
2230
  delay: (err, attempt) => {
2189
2231
  this.store.dispatch(signalingRetryAttempted({
@@ -2279,6 +2321,9 @@ class SignallingService extends SubService {
2279
2321
  wsUrl: httpUrlToWs(`${location.protocol}//${data.signallingServer}`),
2280
2322
  pollingUrl: url,
2281
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,
2282
2327
  })), finalize(() => {
2283
2328
  this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...(lastResp ?? {}), multi: true });
2284
2329
  this.store.dispatch(pollingEnded());
@@ -2396,8 +2441,15 @@ class SignallingService extends SubService {
2396
2441
  *
2397
2442
  * "Just connect" — nothing is sent on open (the machine is already reserved
2398
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.
2399
2451
  */
2400
- connectToStreamMachine(originInstanceUri) {
2452
+ connectToStreamMachine(originInstanceUri, streamRequestId) {
2401
2453
  if (!originInstanceUri) {
2402
2454
  Logger.warn('instanceReady without originInstanceUri — stream socket not opened');
2403
2455
  return;
@@ -2412,7 +2464,7 @@ class SignallingService extends SubService {
2412
2464
  // before a fresh attempt — e.g. a retry that resolved to a different machine
2413
2465
  // or two instanceReady messages before the first connect resolves.
2414
2466
  this.teardownStreamWs();
2415
- this.streamWsSub = createWebSocket(originInstanceUri)
2467
+ this.streamWsSub = createWebSocket(withStreamRequestId(originInstanceUri, streamRequestId))
2416
2468
  .pipe(takeUntil(this.abort$), takeUntil(this.destroy$), take(1))
2417
2469
  .subscribe({
2418
2470
  next: (ws) => {
@@ -3788,6 +3840,7 @@ class StreamAdoptionService {
3788
3840
  this.store.dispatch(streamAdopted({
3789
3841
  machineId: parked.machineId ?? null,
3790
3842
  ssData: parked.ssData ?? null,
3843
+ streamRequestId: parked.ids?.streamRequestId || null,
3791
3844
  timings: parked.timings ?? null,
3792
3845
  }));
3793
3846
  // Hand the live socket over without re-sending `requestStream`, reusing the
@@ -4089,9 +4142,12 @@ class UnrealEffects {
4089
4142
  return this.actions$.pipe(ofType(setAwsInstance),
4090
4143
  // require wsUrl present on the action payload
4091
4144
  filter((action) => !!action.wsUrl),
4092
- // for each awsInstance action capture one ssData snapshot, then wait for dataChannelConnected
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.
4093
4149
  switchMap((awsInstance) => {
4094
- return this.actions$.pipe(ofType(updateCirrusInfo), take(1), switchMap(({ ssData }) => {
4150
+ return this.actions$.pipe(ofType(updateCirrusInfo), take(1), withLatestFrom(this.store.select(selectCirrusConnectionId)), switchMap(([, connectionId]) => {
4095
4151
  const abort$ = this.actions$.pipe(ofType(setCirrusDisconnected, // ← "Cirrus closed" action — direct,
4096
4152
  // no fan-in tick delay
4097
4153
  abortEstablishingConnection), take(1));
@@ -4108,7 +4164,7 @@ class UnrealEffects {
4108
4164
  Logger.error(error);
4109
4165
  const body = {
4110
4166
  awsInstance,
4111
- connectionId: ssData?.connectionId,
4167
+ connectionId,
4112
4168
  href: location.href,
4113
4169
  userAgent: navigator.userAgent,
4114
4170
  error,