@desklink/react-native 0.5.7 → 0.5.8
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 +11 -2
- package/package.json +1 -1
- package/src/protocol.ts +7 -0
- package/src/useDesktopSession.ts +37 -7
package/README.md
CHANGED
|
@@ -247,6 +247,14 @@ view.
|
|
|
247
247
|
dies), so a roam longer than the client's own ~15 s `failed` still returns to
|
|
248
248
|
the same session; an SDP or candidate failure, which means the negotiation
|
|
249
249
|
itself is broken, reopens with fresh authority instead of staying stuck.
|
|
250
|
+
A first open whose path never reaches `connected` and never shows a frame
|
|
251
|
+
is different: it fails once with code `never-connected` — on the peer's ICE
|
|
252
|
+
`failed`, or at the 20 s first-frame bound, whichever comes first — closes
|
|
253
|
+
that session and does not reopen, since the same network gives the same
|
|
254
|
+
answer and every reopen restarts the host's capture. Usually the phone has
|
|
255
|
+
no direct route and the app supplied no relay (ICE) servers; `connect()`
|
|
256
|
+
tries again on demand. A first frame that never comes over a path that did
|
|
257
|
+
connect stays a `transport` failure and reopens as before.
|
|
250
258
|
Every event carries its engine session id where the carrier preserves it,
|
|
251
259
|
and the session ignores anything naming a session it no longer holds, so a
|
|
252
260
|
previous generation's queued offer or revocation cannot corrupt or kill a
|
|
@@ -288,8 +296,9 @@ view.
|
|
|
288
296
|
status by itself: the status follows the picture and the peer's transport, so
|
|
289
297
|
it reads `reconnecting` only once the picture stops or the stall watchdog
|
|
290
298
|
fires. While the loop runs, a session that has shown a picture never reports
|
|
291
|
-
`failed` or a transport failure; one that never showed a picture reports
|
|
292
|
-
first-frame
|
|
299
|
+
`failed` or a transport failure; one that never showed a picture reports its
|
|
300
|
+
first-frame failure after 20 s (`never-connected` when its path never
|
|
301
|
+
formed) and the loop stops. A new carrier
|
|
293
302
|
picks up the same session after a short grace for the old ICE pair, sized by
|
|
294
303
|
how long the picture has been still: stills under 4 s wait out the blip
|
|
295
304
|
(200 ms), stills past it take one stats beat (50 ms) and ask for the restart
|
package/package.json
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -159,7 +159,14 @@ export type SessionFailure =
|
|
|
159
159
|
| { code: 'incompatible-version'; message: string }
|
|
160
160
|
| { code: 'source-changed'; message: string }
|
|
161
161
|
| { code: 'revoked'; message: string }
|
|
162
|
+
/** A path that formed and was lost; the session reopens on its own. */
|
|
162
163
|
| { code: 'transport'; message: string }
|
|
164
|
+
/**
|
|
165
|
+
* A first open whose media path never reached `connected`: no frame, and
|
|
166
|
+
* no reopen, because the same network gives the same answer. Usually the
|
|
167
|
+
* phone has no direct route and the app supplied no relay (ICE) servers.
|
|
168
|
+
*/
|
|
169
|
+
| { code: 'never-connected'; message: string }
|
|
163
170
|
| { code: 'input-unavailable'; message: string }
|
|
164
171
|
| { code: 'platform'; message: string };
|
|
165
172
|
|
package/src/useDesktopSession.ts
CHANGED
|
@@ -222,6 +222,13 @@ const UNREACHABLE_DESKTOP =
|
|
|
222
222
|
export const FIRST_FRAME_AFTER_MS = 20_000;
|
|
223
223
|
const NO_PICTURE =
|
|
224
224
|
'The desktop never sent a picture. On a path that only reaches the desktop through a tunnel or a USB forward, open the session with loopbackTcp: true.';
|
|
225
|
+
/**
|
|
226
|
+
* A first open whose media path never formed: no candidate pair ever answered.
|
|
227
|
+
* Reopening asks the same network the same question, and every reopen makes the
|
|
228
|
+
* host restart its capture, so this ends the session once instead.
|
|
229
|
+
*/
|
|
230
|
+
const NEVER_CONNECTED =
|
|
231
|
+
'No network path to the desktop ever formed. Other networks need a relay route supplied by the app; a tunnel or USB forward needs loopbackTcp: true.';
|
|
225
232
|
|
|
226
233
|
/**
|
|
227
234
|
* The engine's and host's own refusal tokens, mapped to this package's failure
|
|
@@ -407,6 +414,10 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
407
414
|
const restartCycles = useRef(0);
|
|
408
415
|
/** The last ICE state the peer or the engine reported. */
|
|
409
416
|
const iceConnected = useRef(false);
|
|
417
|
+
/** Whether the held native session's path ever reached `connected`. */
|
|
418
|
+
const pathFormed = useRef(false);
|
|
419
|
+
/** Whether any session since the last teardown showed a picture: reopens are for those only. */
|
|
420
|
+
const everPresented = useRef(false);
|
|
410
421
|
/**
|
|
411
422
|
* When the peer's own ICE last failed while the session was held, or null
|
|
412
423
|
* when the current failure is not an ICE one. Kept across the hold so the
|
|
@@ -465,10 +476,17 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
465
476
|
* `ice` marks a failure of the peer's own media path (its ICE `failed`
|
|
466
477
|
* state, or a `failure` event whose reason is `ice`). While the session is
|
|
467
478
|
* held and has not been revoked, that is not the end of the session: the
|
|
468
|
-
* reopen effect keeps it for `HOLD_AFTER_FAILED_MS` first.
|
|
479
|
+
* reopen effect keeps it for `HOLD_AFTER_FAILED_MS` first.
|
|
480
|
+
*
|
|
481
|
+
* A first open whose path never formed is neither: there is nothing to
|
|
482
|
+
* hold and nothing a reopen would change, so it ends as `never-connected`. */
|
|
469
483
|
const transportFailed = useCallback((message: string = UNREACHABLE_DESKTOP, ice = false): boolean => {
|
|
470
484
|
if (dialing.current !== null) return false;
|
|
471
485
|
if (ice && statusRef.current === 'failed') return false;
|
|
486
|
+
if (!everPresented.current && !pathFormed.current && nativeRef.current != null) {
|
|
487
|
+
refuse(NEVER_CONNECTED, 'never-connected');
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
472
490
|
refuse(message, 'transport', ice);
|
|
473
491
|
return true;
|
|
474
492
|
}, [refuse]);
|
|
@@ -671,6 +689,7 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
671
689
|
const onTransportState = useCallback((state: string, trackIce = true, skipGrace = false) => {
|
|
672
690
|
// Synthetic stall reports say nothing about ICE itself.
|
|
673
691
|
if (trackIce) iceConnected.current = state === 'connected' || state === 'completed';
|
|
692
|
+
if (trackIce && iceConnected.current) pathFormed.current = true;
|
|
674
693
|
if (heldIceFailureAt.current !== null && statusRef.current === 'failed'
|
|
675
694
|
&& (state === 'connected' || state === 'completed')) {
|
|
676
695
|
heldIceFailureAt.current = null;
|
|
@@ -907,6 +926,7 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
907
926
|
pendingCandidates.current = [];
|
|
908
927
|
pendingCursorEvents.current = [];
|
|
909
928
|
heldIceFailureAt.current = null;
|
|
929
|
+
pathFormed.current = false;
|
|
910
930
|
restartGraceUntil.current = 0;
|
|
911
931
|
restartCycles.current = 0;
|
|
912
932
|
// A new session has its own media path: the last one's counts and its
|
|
@@ -949,6 +969,7 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
949
969
|
cancelFirstFrame();
|
|
950
970
|
stopDialing();
|
|
951
971
|
restoreToken.current = null;
|
|
972
|
+
everPresented.current = false;
|
|
952
973
|
const openedRef = opened.current;
|
|
953
974
|
const owner = signaling.current;
|
|
954
975
|
discardSession();
|
|
@@ -1288,6 +1309,7 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
1288
1309
|
// One mark per session: a producer that re-marks (a repaint
|
|
1289
1310
|
// after renegotiation) must not read a stale picture as live.
|
|
1290
1311
|
if (presentedRef.current) return;
|
|
1312
|
+
everPresented.current = true;
|
|
1291
1313
|
attempts.current = 0;
|
|
1292
1314
|
restartAttempts.current = 0;
|
|
1293
1315
|
restartCycles.current = 0;
|
|
@@ -1449,17 +1471,25 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
|
|
|
1449
1471
|
heldIceFailureAt.current = null;
|
|
1450
1472
|
return;
|
|
1451
1473
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
// native handle that makes `connect()` a no-op.
|
|
1474
|
+
// The failed session is dead on the engine side. Drop its handle and
|
|
1475
|
+
// close the remote session whether or not another attempt is left: a
|
|
1476
|
+
// failure the user can retry by hand must not sit behind a stale
|
|
1477
|
+
// native handle that makes `connect()` a no-op.
|
|
1478
|
+
const drop = () => {
|
|
1458
1479
|
const owner = signaling.current;
|
|
1459
1480
|
const openedRef = opened.current;
|
|
1460
1481
|
cancelRestart();
|
|
1461
1482
|
discardSession();
|
|
1462
1483
|
void endRemote(openedRef, owner);
|
|
1484
|
+
};
|
|
1485
|
+
// A path that never formed is closed once and not reopened.
|
|
1486
|
+
if (snapshot.failure?.code === 'never-connected') {
|
|
1487
|
+
drop();
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (snapshot.failure?.code !== 'transport') return;
|
|
1491
|
+
const reopen = () => {
|
|
1492
|
+
drop();
|
|
1463
1493
|
// With the carrier dead, the dial loop opens on the first one that comes up.
|
|
1464
1494
|
if (dialing.current !== null) {
|
|
1465
1495
|
update({ status: 'reconnecting' });
|