@desklink/react-native 0.3.1 → 0.4.0

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
@@ -58,12 +58,12 @@ changes, and trackpad presses reach the desktop as touches.
58
58
  ## Use
59
59
 
60
60
  ```tsx
61
- import { DesktopView, useDesktopSession, CONTROL_PERMISSIONS } from '@desklink/react-native';
61
+ import { DesktopView, useDesktopSession } from '@desklink/react-native';
62
62
 
63
63
  const desktop = useDesktopSession({
64
64
  authorize: async () => ({
65
65
  signaling, // your authenticated channel to the host
66
- session: { permissions: CONTROL_PERMISSIONS },
66
+ session: {},
67
67
  }),
68
68
  });
69
69
 
@@ -78,6 +78,13 @@ desktop.setOrientation('landscape'); // Android only; iOS apps configure suppo
78
78
  await desktop.pasteLocalToRemote(await Clipboard.getStringAsync());
79
79
  ```
80
80
 
81
+ Pairing is the only trust boundary. `authorize` is the app's pairing check: it
82
+ returns a channel to a paired computer or throws, and it runs again on every
83
+ reconnect, so a revoked pairing cannot reopen. A paired session has view,
84
+ input and clipboard; there are no in-app grants, and `session.permissions` is
85
+ ignored. Only the operating system's own consent (screen recording,
86
+ accessibility, a screen-sharing prompt) still applies on the computer.
87
+
81
88
  A screen that only decides *whether* to offer a desktop does not need the
82
89
  session: import the flag from its own entry, which is a constant on web and a
83
90
  check for the platform module elsewhere, so the hook and the view stay out of
@@ -98,7 +105,7 @@ engine only produces frames on change.
98
105
  Keep the encoded picture inside a 1920×1080 box:
99
106
 
100
107
  ```tsx
101
- session: { permissions: CONTROL_PERMISSIONS, maxWidth: 1920, maxHeight: 1080 },
108
+ session: { maxWidth: 1920, maxHeight: 1080 },
102
109
  ```
103
110
 
104
111
  The engine never upscales, so a smaller desktop is sent at its own size and a
@@ -212,11 +219,13 @@ heat and battery.
212
219
  network is still down. Renegotiation transients from a restart the session
213
220
  asked for itself are expected, not a new outage: they neither flip the
214
221
  status nor trigger another restart for a short grace window.
215
- - **Control starts off.** `setInputEnabled(true)` enables pointer, keyboard and
216
- clipboard input; `setInputEnabled(false)` blocks new input, releases held keys
217
- and buttons, and resets pending gestures. The app decides when to enable it
218
- after a deliberate action and must disable it on background or lost readiness;
219
- a clipboard write already in flight may still finish.
222
+ - **Input starts disarmed.** This is a local arm against accidental touches,
223
+ not a permission: the session already has input. `setInputEnabled(true)` arms
224
+ pointer, keyboard and clipboard input; `setInputEnabled(false)` blocks new
225
+ input, releases held keys and buttons, and resets pending gestures. The app
226
+ decides when to arm it after a deliberate action and must disarm it on
227
+ background or lost readiness; a clipboard write already in flight may still
228
+ finish.
220
229
  - **Released state.** The app disables input on background and unmount;
221
230
  session close and a lost control channel release what the desktop was holding.
222
231
  - **Explicit clipboard.** Two methods, called on a user action while control is
@@ -278,7 +287,11 @@ What the consuming app — not this package — must get right before review.
278
287
 
279
288
  `example/` is the smallest app that shows a desktop: a native (non-Expo Go)
280
289
  build with `react-native-webrtc`, connecting to `desklink-host bridge` with the
281
- URL it is launched with. `test/ios-flow.mjs` builds it for the iOS simulator on
290
+ URL it is launched with. Its status line shows plain messages: `Connected`
291
+ once a frame is presented, `Can’t reach your desktop. Reconnecting…` while
292
+ retrying, and `Your desktop ended this session` after revocation. It never
293
+ displays the bridge URL, pairing token, raw failure text or frame metrics.
294
+ `test/ios-flow.mjs` builds it for the iOS simulator on
282
295
  a Mac over ssh, serves a private Xvfb desktop from this machine through the
283
296
  bridge, and checks that the first frame is presented, that the answer puts
284
297
  H.264 first with NACK when the host offers it (VP9 otherwise; both descriptions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desklink/react-native",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Render and drive a desktop session from a React Native app",
6
6
  "main": "index.ts",
package/src/protocol.ts CHANGED
@@ -16,6 +16,11 @@
16
16
 
17
17
  export const PROTOCOL_VERSION = 3;
18
18
 
19
+ /**
20
+ * @deprecated Pairing is the only trust boundary: a paired session always has
21
+ * view, input and clipboard, so there is nothing to ask for. Kept so 0.3 code
22
+ * compiles; removed at 1.0.
23
+ */
19
24
  export type Permission = 'view' | 'control' | 'clipboard';
20
25
 
21
26
  /**
@@ -42,7 +47,8 @@ export interface SurfaceGeometry {
42
47
 
43
48
  export interface SessionOpenRequest {
44
49
  cursor?: 'embedded' | 'hidden';
45
- permissions: Permission[];
50
+ /** @deprecated Ignored: a paired session always has view, input and clipboard. */
51
+ permissions?: Permission[];
46
52
  maxWidth?: number;
47
53
  maxHeight?: number;
48
54
  bitrateKbps?: number;
@@ -127,6 +133,7 @@ export type SessionStatus =
127
133
  | 'failed';
128
134
 
129
135
  export type SessionFailure =
136
+ /** The app's `authorize()` refused, or the host did: not paired, or the pairing was revoked. */
130
137
  | { code: 'permission'; message: string }
131
138
  /** Nobody approved the desktop's own screen-sharing prompt in time. */
132
139
  | { code: 'consent'; message: string }
@@ -146,6 +153,12 @@ export interface SessionSnapshot {
146
153
  geometry: SurfaceGeometry | null;
147
154
  /** True once an authorized, current-generation frame has actually rendered. */
148
155
  presented: boolean;
156
+ /**
157
+ * `message` is free text from the app's `authorize()`, the host or the
158
+ * engine and may contain a bridge URL or pairing token. Never display it
159
+ * to people; redact sensitive data before logging it. Choose user-facing
160
+ * text from the session state and `code` instead.
161
+ */
149
162
  failure: SessionFailure | null;
150
163
  /** Redacted diagnostics: codec, route, counters. Never identities. */
151
164
  diagnostics: Record<string, string | number | boolean>;
@@ -172,10 +172,10 @@ async function loadPlatform(): Promise<boolean> {
172
172
 
173
173
  export interface DesktopSessionOptions {
174
174
  /**
175
- * Ask the owning application for a fresh, short-lived authorization and the
176
- * authenticated channel to the host engine. Called for the first connection
177
- * and again for a reconnect: the package never reuses authority it was not
178
- * granted again.
175
+ * The app's pairing check: return the authenticated channel to the host
176
+ * engine of a paired computer, or throw. Pairing is the only trust
177
+ * boundary, so a paired session has view, input and clipboard. Called again
178
+ * on every reconnect, so a revoked pairing cannot reopen.
179
179
  */
180
180
  authorize: () => Promise<{ signaling: Signaling; session: SessionOpenRequest }>;
181
181
  onStateChange?: (snapshot: SessionSnapshot) => void;
@@ -717,7 +717,10 @@ export function useDesktopSession(options: DesktopSessionOptions): DesktopSessio
717
717
  let openedResult: SessionOpenResult;
718
718
  try {
719
719
  openedResult = await authorization.signaling.request<SessionOpenResult>('session.open', {
720
- permissions: authorization.session.permissions,
720
+ // Ignored by 0.4+ engines, whatever the app asked for. A 0.3
721
+ // engine refuses an open without `view`, so the full set stays
722
+ // on the wire until 0.3 hosts leave support.
723
+ permissions: ['view', 'control', 'clipboard'],
721
724
  max_width: authorization.session.maxWidth,
722
725
  ...(authorization.session.cursor === undefined ? {} : { cursor: authorization.session.cursor }),
723
726
  max_height: authorization.session.maxHeight,
@@ -1106,5 +1109,5 @@ function randomId(): string {
1106
1109
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1107
1110
  }
1108
1111
 
1109
- /** The permissions a normal remote-control session asks for. */
1112
+ /** @deprecated Ignored: a paired session always has view, input and clipboard. Removed at 1.0. */
1110
1113
  export const CONTROL_PERMISSIONS: Permission[] = ['view', 'control', 'clipboard'];