@shenora/react 0.10.0 → 0.12.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 +152 -165
- package/dist/bridge.d.ts +29 -46
- package/dist/bridge.js +47 -71
- package/dist/clipboard.d.ts +89 -0
- package/dist/clipboard.js +119 -0
- package/dist/devInterceptor.d.ts +11 -8
- package/dist/devInterceptor.js +16 -10
- package/dist/errors.d.ts +5 -6
- package/dist/errors.js +6 -7
- package/dist/eventBus.d.ts +21 -26
- package/dist/eventBus.js +38 -40
- package/dist/fileDialogs.d.ts +9 -12
- package/dist/fileDialogs.js +12 -16
- package/dist/hooks.d.ts +19 -20
- package/dist/hooks.js +26 -29
- package/dist/index.d.ts +8 -2
- package/dist/index.js +14 -10
- package/dist/internal.d.ts +2 -8
- package/dist/internal.js +2 -8
- package/dist/media.d.ts +14 -22
- package/dist/media.js +14 -22
- package/dist/mediaPlayer.d.ts +103 -0
- package/dist/mediaPlayer.js +202 -0
- package/dist/moduleService.d.ts +17 -21
- package/dist/moduleService.js +17 -21
- package/dist/requests.d.ts +145 -0
- package/dist/requests.js +113 -0
- package/dist/segmentBinder.d.ts +74 -0
- package/dist/segmentBinder.js +239 -0
- package/dist/segmentStream.d.ts +125 -0
- package/dist/segmentStream.js +239 -0
- package/dist/store.d.ts +18 -26
- package/dist/store.js +69 -36
- package/dist/transport.d.ts +9 -18
- package/dist/transport.js +9 -18
- package/dist/types.d.ts +33 -42
- package/dist/types.js +29 -25
- package/dist/useDropZone.d.ts +23 -22
- package/dist/useDropZone.js +41 -37
- package/dist/windowCommands.d.ts +15 -19
- package/dist/windowCommands.js +18 -25
- package/package.json +10 -3
- package/dist/operations.d.ts +0 -256
- package/dist/operations.js +0 -191
package/README.md
CHANGED
|
@@ -1,165 +1,152 @@
|
|
|
1
|
-
# @shenora/react
|
|
2
|
-
|
|
3
|
-
React client for [Shenora](https://github.com/JiarongGu/Shenora) desktop hosts (.NET + WinForms +
|
|
4
|
-
WebView2). The typed bridge between a React frontend and the Shenora host: correlated `invoke`
|
|
5
|
-
with timeouts and structured errors, the event hub host notifications stream into, typed module
|
|
6
|
-
services, React hooks, and a pluggable transport with a browser fallback so the UI can be
|
|
7
|
-
developed in a plain browser. Headless by design — no UI components, bring your own design
|
|
8
|
-
system. Versioned in lockstep with the `Shenora.*` NuGet packages.
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
import {
|
|
12
|
-
getBridge, useShenoraEvent, useShenoraQuery, BaseModuleService, createShenoraStore,
|
|
13
|
-
} from '@shenora/react';
|
|
14
|
-
|
|
15
|
-
// Once at startup, after your listeners are attached: it starts notification delivery (anything the
|
|
16
|
-
// host buffered arrives in the first batch). Drop zones need no particular ordering against it — the
|
|
17
|
-
// host clears them when a new DOCUMENT loads, not on this handshake.
|
|
18
|
-
await getBridge().notifyReady();
|
|
19
|
-
|
|
20
|
-
// a typed service per backend module:
|
|
21
|
-
interface NoteRequests { GET_ALL: void; ADD: { title: string } }
|
|
22
|
-
class NoteService extends BaseModuleService<NoteRequests> {
|
|
23
|
-
constructor() { super('NOTES'); }
|
|
24
|
-
getAll() { return this.send<Note[]>('GET_ALL'); }
|
|
25
|
-
add(title: string) { return this.send<Note>('ADD', { payload: { title } }); }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// in components:
|
|
29
|
-
const { data, loading, refetch } = useShenoraQuery<Note[]>('NOTES', 'GET_ALL');
|
|
30
|
-
useShenoraEvent<Note>('NOTES', 'ADDED', (note) => refetch());
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Failed calls reject with `
|
|
34
|
-
`errors.{code}`) plus interpolation `parameters`, never raw host exception text.
|
|
35
|
-
|
|
36
|
-
### Long-running work: post, then read a store
|
|
37
|
-
|
|
38
|
-
`invoke` awaits a correlated reply and carries a timeout, and its handler's synchronous segment runs
|
|
39
|
-
on the host's **UI thread** — so it is for calls that are quick and UI-thread-safe. Everything else
|
|
40
|
-
posts and streams results back as events:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
const useDeploy = createShenoraStore('DEPLOY', {
|
|
44
|
-
initial: { status: 'idle', lines: [] as string[] },
|
|
45
|
-
// Loaded on the FIRST subscriber, so a component that mounts mid-run isn't empty:
|
|
46
|
-
// events it missed cannot be replayed.
|
|
47
|
-
snapshot: { type: 'GET_STATE', apply: (s, d) => ({ ...s, ...(d as object) }) },
|
|
48
|
-
on: {
|
|
49
|
-
PROGRESS: (s, p: { line: string }) => ({ ...s, lines: [...s.lines, p.line] }),
|
|
50
|
-
ENDED: (s, p: { ok: boolean }) => ({ ...s, status: p.ok ? 'done' : 'failed' }),
|
|
51
|
-
},
|
|
52
|
-
actions: ({ post }) => ({ start: (cfg: unknown) => post('START', { payload: cfg }) }),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// any number of components share ONE subscription:
|
|
56
|
-
const status = useDeploy((s) => s.status);
|
|
57
|
-
useDeploy.actions.start({ env: 'prod' });
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Use the **store for shared or long-lived state** and **`useShenoraEvent` for a one-off reaction in a
|
|
61
|
-
single component**. A failed `post` has no promise to reject, so it is reported through the bridge's
|
|
62
|
-
`onPostError` rather than vanishing — it is bridge-wide, so wire it once at startup
|
|
63
|
-
(`getBridge()` takes no options):
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
configureBridge({ onPostError: (failure) => log.error(failure.module, failure.type, failure.error) });
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
###
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
import type
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Delivery is narrowest-first — exact pair, then module, then catch-all — so a broad observer never
|
|
156
|
-
runs ahead of the feature code it is observing. Prefer `subscribe` when you know the pair: a
|
|
157
|
-
catch-all wakes for every event on the bus.
|
|
158
|
-
|
|
159
|
-
Pure-UI development in a plain browser: pass a `fallback` to `configureBridge` (gated behind
|
|
160
|
-
`import.meta.env.DEV`) to answer requests with canned data. Other shells (WebSocket,
|
|
161
|
-
mobile/Capacitor) implement the small `ShenoraTransport` seam and speak the same envelopes.
|
|
162
|
-
For CDP-driven testing, `installDevInterceptor()` records IPC/event traffic into ring buffers
|
|
163
|
-
and exposes `window.__shenora.call()/waitEvent()`.
|
|
164
|
-
|
|
165
|
-
MIT © Jiarong Gu
|
|
1
|
+
# @shenora/react
|
|
2
|
+
|
|
3
|
+
React client for [Shenora](https://github.com/JiarongGu/Shenora) desktop hosts (.NET + WinForms +
|
|
4
|
+
WebView2). The typed bridge between a React frontend and the Shenora host: correlated `invoke`
|
|
5
|
+
with timeouts and structured errors, the event hub host notifications stream into, typed module
|
|
6
|
+
services, React hooks, and a pluggable transport with a browser fallback so the UI can be
|
|
7
|
+
developed in a plain browser. Headless by design — no UI components, bring your own design
|
|
8
|
+
system. Versioned in lockstep with the `Shenora.*` NuGet packages.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import {
|
|
12
|
+
getBridge, useShenoraEvent, useShenoraQuery, BaseModuleService, createShenoraStore,
|
|
13
|
+
} from '@shenora/react';
|
|
14
|
+
|
|
15
|
+
// Once at startup, after your listeners are attached: it starts notification delivery (anything the
|
|
16
|
+
// host buffered arrives in the first batch). Drop zones need no particular ordering against it — the
|
|
17
|
+
// host clears them when a new DOCUMENT loads, not on this handshake.
|
|
18
|
+
await getBridge().notifyReady();
|
|
19
|
+
|
|
20
|
+
// a typed service per backend module:
|
|
21
|
+
interface NoteRequests { GET_ALL: void; ADD: { title: string } }
|
|
22
|
+
class NoteService extends BaseModuleService<NoteRequests> {
|
|
23
|
+
constructor() { super('NOTES'); }
|
|
24
|
+
getAll() { return this.send<Note[]>('GET_ALL'); }
|
|
25
|
+
add(title: string) { return this.send<Note>('ADD', { payload: { title } }); }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// in components:
|
|
29
|
+
const { data, loading, refetch } = useShenoraQuery<Note[]>('NOTES', 'GET_ALL');
|
|
30
|
+
useShenoraEvent<Note>('NOTES', 'ADDED', (note) => refetch());
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Failed calls reject with `ShenoraError` — a structured `code` (an i18n key: translate
|
|
34
|
+
`errors.{code}`) plus interpolation `parameters`, never raw host exception text.
|
|
35
|
+
|
|
36
|
+
### Long-running work: post, then read a store
|
|
37
|
+
|
|
38
|
+
`invoke` awaits a correlated reply and carries a timeout, and its handler's synchronous segment runs
|
|
39
|
+
on the host's **UI thread** — so it is for calls that are quick and UI-thread-safe. Everything else
|
|
40
|
+
posts and streams results back as events:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const useDeploy = createShenoraStore('DEPLOY', {
|
|
44
|
+
initial: { status: 'idle', lines: [] as string[] },
|
|
45
|
+
// Loaded on the FIRST subscriber, so a component that mounts mid-run isn't empty:
|
|
46
|
+
// events it missed cannot be replayed.
|
|
47
|
+
snapshot: { type: 'GET_STATE', apply: (s, d) => ({ ...s, ...(d as object) }) },
|
|
48
|
+
on: {
|
|
49
|
+
PROGRESS: (s, p: { line: string }) => ({ ...s, lines: [...s.lines, p.line] }),
|
|
50
|
+
ENDED: (s, p: { ok: boolean }) => ({ ...s, status: p.ok ? 'done' : 'failed' }),
|
|
51
|
+
},
|
|
52
|
+
actions: ({ post }) => ({ start: (cfg: unknown) => post('START', { payload: cfg }) }),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// any number of components share ONE subscription:
|
|
56
|
+
const status = useDeploy((s) => s.status);
|
|
57
|
+
useDeploy.actions.start({ env: 'prod' });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use the **store for shared or long-lived state** and **`useShenoraEvent` for a one-off reaction in a
|
|
61
|
+
single component**. A failed `post` has no promise to reject, so it is reported through the bridge's
|
|
62
|
+
`onPostError` rather than vanishing — it is bridge-wide, so wire it once at startup
|
|
63
|
+
(`getBridge()` takes no options):
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
configureBridge({ onPostError: (failure) => log.error(failure.module, failure.type, failure.error) });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Requests in flight: a ready-made progress store
|
|
70
|
+
|
|
71
|
+
Every request the host handles is tracked automatically — there is nothing to declare on either side.
|
|
72
|
+
`useShenoraRequests()` is a `createShenoraStore` instance built the same way as the example above:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { useShenoraRequests } from '@shenora/react';
|
|
76
|
+
|
|
77
|
+
const running = useShenoraRequests((s) => s.running); // every request still in flight
|
|
78
|
+
const finished = useShenoraRequests((s) => s.finished); // retained history, newest first
|
|
79
|
+
const importJob = useShenoraRequests((s) => s.byId[requestId]); // one, by id
|
|
80
|
+
|
|
81
|
+
useShenoraRequests.actions.cancel(requestId); // XMLHttpRequest.abort() — the id you sent with
|
|
82
|
+
useShenoraRequests.actions.clearFinished();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
🔴 **The id is the one you already have.** `requestId` is the `id` of the request you sent — there is no
|
|
86
|
+
second identity to correlate. Cancelling it targets the token the route is running under.
|
|
87
|
+
|
|
88
|
+
⚠ **Most requests never appear here, and that is the design.** The host stays SILENT for the first
|
|
89
|
+
50 ms (`IpcRequestTrackerOptions.GracePeriod`): a request that finishes inside that window emits no
|
|
90
|
+
event at all — no running snapshot, no completion, nothing retained. So this store is a list of work
|
|
91
|
+
that is actually *taking a while*, not a log of every call your page made. Nobody wants a spinner for
|
|
92
|
+
five milliseconds of work, and the clock decides that at run time rather than a module author guessing
|
|
93
|
+
at authoring time.
|
|
94
|
+
|
|
95
|
+
`request.progress` is an exported `IpcProgress` (`{ value: number; total?: number; unit?: string }`) —
|
|
96
|
+
import the type rather than re-declaring the shape. It is the APP's own unit (bytes of a known total,
|
|
97
|
+
items of a known total, an absolute count with no known total, or a genuine percent), never a
|
|
98
|
+
kit-assumed percentage: `total` is the denominator when one is known and `undefined` when there is
|
|
99
|
+
none, and `unit` is app-defined and uninterpreted. The kit ships no percent helper — render a ratio
|
|
100
|
+
only when you have a `total`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import type { IpcProgress } from '@shenora/react';
|
|
104
|
+
|
|
105
|
+
function format(progress?: IpcProgress): string {
|
|
106
|
+
if (!progress) return 'starting…';
|
|
107
|
+
return progress.total
|
|
108
|
+
? `${Math.round((progress.value / progress.total) * 100)}%`
|
|
109
|
+
: `${progress.value}${progress.unit ? ` ${progress.unit}` : ''}`; // no known total
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
That division is your own policy, not the kit's, which is why it lives here instead of in `src/`.
|
|
114
|
+
|
|
115
|
+
The store snapshots via `LIST` on first subscribe (so a progress strip that mounts mid-run is not
|
|
116
|
+
empty), then folds `REQUEST_UPDATED` by id — one subscription however many components read it. Two
|
|
117
|
+
bands, both derived from `byId` on every read: `running` and `finished`. Filtering by your own
|
|
118
|
+
`module`/`type` is a plain `Array.filter` over either.
|
|
119
|
+
|
|
120
|
+
`clearFinished` does not touch local state itself: the host's `REQUEST_REMOVED { requestIds }` is the
|
|
121
|
+
one authoritative removal signal the store folds, deleting exactly the named ids. History eviction and
|
|
122
|
+
`clearFinished` both publish it, so a long-lived store's mirror of bounded host history cannot drift
|
|
123
|
+
from what the host actually did.
|
|
124
|
+
|
|
125
|
+
⚠ **Work nobody requested does not belong here.** A scheduled job, a background sync, anything the host
|
|
126
|
+
starts on its own — those have no request behind them and no response to wait for, so they report on
|
|
127
|
+
their own event stream via `useShenoraEvent`. Squeezing them in here is what the previous design did,
|
|
128
|
+
and it is why it needed a "waiting" state nothing else could explain.
|
|
129
|
+
|
|
130
|
+
### Observing the whole stream
|
|
131
|
+
|
|
132
|
+
`useShenoraEvent` and `createShenoraStore` listen for an exact `(module, type)`. When the vocabulary
|
|
133
|
+
isn't knowable up front — plug-in-contributed events, a diagnostics tap, or an adoption shim keeping
|
|
134
|
+
a legacy "every host message" handler alive while features migrate one at a time — subscribe broadly
|
|
135
|
+
instead. Both mirror the host's `IEventBus` and return an unsubscribe:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const off = eventBus.subscribeToAll((event) => log.debug(event.module, event.type, event.payload));
|
|
139
|
+
eventBus.subscribeToModule('DEPLOY', (event) => audit(event)); // every type from one module
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Delivery is narrowest-first — exact pair, then module, then catch-all — so a broad observer never
|
|
143
|
+
runs ahead of the feature code it is observing. Prefer `subscribe` when you know the pair: a
|
|
144
|
+
catch-all wakes for every event on the bus.
|
|
145
|
+
|
|
146
|
+
Pure-UI development in a plain browser: pass a `fallback` to `configureBridge` (gated behind
|
|
147
|
+
`import.meta.env.DEV`) to answer requests with canned data. Other shells (WebSocket,
|
|
148
|
+
mobile/Capacitor) implement the small `ShenoraTransport` seam and speak the same envelopes.
|
|
149
|
+
For CDP-driven testing, `installDevInterceptor()` records IPC/event traffic into ring buffers
|
|
150
|
+
and exposes `window.__shenora.call()/waitEvent()`.
|
|
151
|
+
|
|
152
|
+
MIT © Jiarong Gu
|
package/dist/bridge.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface ShenoraBridgeOptions {
|
|
|
6
6
|
/**
|
|
7
7
|
* The channel to the host. Default: whichever Shenora host this page is in — WebView2 postMessage
|
|
8
8
|
* on the desktop shell, `HybridWebView` on the MAUI shell — else null (plain browser). Supply your
|
|
9
|
-
* own for another shell
|
|
9
|
+
* own for another shell, or a scripted fake for tests and preview harnesses.
|
|
10
10
|
*/
|
|
11
11
|
transport?: ShenoraTransport | null;
|
|
12
12
|
/** The event bus host notifications are unbundled into. Default: the shared bus. */
|
|
@@ -14,25 +14,23 @@ export interface ShenoraBridgeOptions {
|
|
|
14
14
|
/** Per-request timeout in ms when the call doesn't set one. Family default: 30 000. */
|
|
15
15
|
defaultTimeoutMs?: number;
|
|
16
16
|
/**
|
|
17
|
-
* Pure-UI development seam: answers requests when NO transport exists (plain browser tab
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* production stays hard-failing.
|
|
17
|
+
* Pure-UI development seam: answers requests when NO transport exists (a plain browser tab).
|
|
18
|
+
* Return the response data, or a promise; throw to reject.
|
|
19
|
+
*
|
|
20
|
+
* ⚠ Gate the call site with `import.meta.env.DEV` so production stays hard-failing.
|
|
22
21
|
*/
|
|
23
22
|
fallback?: (request: IpcRequest) => unknown;
|
|
24
23
|
/**
|
|
25
24
|
* Where a FAILED {@link ShenoraBridge.post} is reported. Default: `console.error`.
|
|
26
25
|
*
|
|
27
|
-
* A one-way send has no promise to reject, so
|
|
28
|
-
*
|
|
29
|
-
* "just stops working" with nothing to grep for. Route it into the app's logger/toast instead.
|
|
26
|
+
* ⚠ Route it into the app's logger or toast. A one-way send has no promise to reject, so its
|
|
27
|
+
* failures are otherwise invisible.
|
|
30
28
|
*/
|
|
31
29
|
onPostError?: (error: PostFailure) => void;
|
|
32
30
|
/**
|
|
33
31
|
* How many unawaited {@link ShenoraBridge.post} ids to remember for error reporting. Default 256.
|
|
34
|
-
* Capped
|
|
35
|
-
*
|
|
32
|
+
* Capped drop-oldest, so a host that never answers cannot grow the set without bound; evicting an
|
|
33
|
+
* id only loses its error report.
|
|
36
34
|
*/
|
|
37
35
|
maxTrackedPosts?: number;
|
|
38
36
|
}
|
|
@@ -59,10 +57,10 @@ export interface PostOptions<TPayload = unknown> {
|
|
|
59
57
|
scope?: string;
|
|
60
58
|
}
|
|
61
59
|
/**
|
|
62
|
-
* The client side of the Shenora IPC contract
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
60
|
+
* The client side of the Shenora IPC contract: correlated request/response over a pluggable
|
|
61
|
+
* transport, category routing of host messages (`ipc` → resolve the pending call, `notification` →
|
|
62
|
+
* unbundle the batch into the event bus), per-request timeout, the ready handshake, and a browser
|
|
63
|
+
* fallback seam for pure-UI development.
|
|
66
64
|
*
|
|
67
65
|
* Most apps use the lazy default instance via {@link getBridge}/{@link configureBridge}; create
|
|
68
66
|
* instances directly for tests or multi-transport setups.
|
|
@@ -82,40 +80,29 @@ export declare class ShenoraBridge {
|
|
|
82
80
|
constructor(options?: ShenoraBridgeOptions);
|
|
83
81
|
/**
|
|
84
82
|
* True when this bridge can actually send: a transport to a host exists AND the bridge has not been
|
|
85
|
-
* disposed.
|
|
86
|
-
* replaced still reported itself available while every `invoke` on it rejected with `NO_TRANSPORT` —
|
|
87
|
-
* the exact case the disposed check in `invoke` exists for (P5.5 H2).
|
|
83
|
+
* disposed. The check to make on a reference that may have outlived a {@link configureBridge} swap.
|
|
88
84
|
*/
|
|
89
85
|
get isAvailable(): boolean;
|
|
90
86
|
/**
|
|
91
87
|
* Send a request and await its typed response data. A failed response rejects with the
|
|
92
|
-
* structured {@link
|
|
88
|
+
* structured {@link ShenoraError} (code + parameters); no response within the timeout
|
|
93
89
|
* rejects with code `TIMEOUT`; no transport and no fallback rejects with `NO_TRANSPORT`.
|
|
94
90
|
*/
|
|
95
91
|
invoke<TData = unknown, TPayload = unknown>(module: string, type: string, options?: InvokeOptions<TPayload>): Promise<TData>;
|
|
96
92
|
/**
|
|
97
93
|
* Send WITHOUT awaiting a reply, and return the request id.
|
|
98
94
|
*
|
|
99
|
-
* This is the default shape for a desktop shell
|
|
100
|
-
* `
|
|
101
|
-
*
|
|
102
|
-
* design, because the dispatch pipeline preserves the caller's synchronization context so facades
|
|
103
|
-
* can touch the window. Reserve `invoke` for calls that are quick AND safe on the UI thread — the
|
|
104
|
-
* window commands are the model — and post everything else, streaming results back as notifications.
|
|
105
|
-
*
|
|
106
|
-
* ⚠ Posting is only HALF of freeing the UI thread. The host still dispatches on the UI thread
|
|
107
|
-
* whether or not the client awaits, so a handler that does heavy work synchronously stalls the
|
|
108
|
-
* window either way. The other half is the host's: return from the route immediately and stream.
|
|
95
|
+
* This is the default shape for a desktop shell and {@link invoke} is the special case (D23):
|
|
96
|
+
* reserve `invoke` for calls that are quick AND safe on the host's UI thread, and post everything
|
|
97
|
+
* else, streaming results back as notifications.
|
|
109
98
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
99
|
+
* A failed response is reported through `onPostError` (default `console.error`) rather than
|
|
100
|
+
* dropped. The id is remembered for that — see {@link ShenoraBridgeOptions.maxTrackedPosts}. No
|
|
101
|
+
* timer is set, so there is no deadline.
|
|
113
102
|
*
|
|
114
|
-
* No transport (a plain browser tab) is a silent no-op,
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* while `post` just returns the id, so a stale reference kept across a `configureBridge` swap looks
|
|
118
|
-
* like it is still sending. `isAvailable` is the check.
|
|
103
|
+
* ⚠ No transport (a plain browser tab) is a silent no-op, and **so is a DISPOSED bridge** — where
|
|
104
|
+
* `invoke` would reject with `NO_TRANSPORT`, this just returns the id, so a stale reference kept
|
|
105
|
+
* across a {@link configureBridge} swap looks like it is still sending. `isAvailable` is the check.
|
|
119
106
|
*/
|
|
120
107
|
post<TPayload = unknown>(module: string, type: string, options?: PostOptions<TPayload>): string;
|
|
121
108
|
/**
|
|
@@ -125,22 +112,18 @@ export declare class ShenoraBridge {
|
|
|
125
112
|
* also the host's cue to reset per-page state. No-transport is a silent no-op so browser dev
|
|
126
113
|
* doesn't error.
|
|
127
114
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* was acked. `DropZoneManager` now clears on DOCUMENT CHANGE instead, which cannot race the client.
|
|
131
|
-
* The general point still stands for any per-page state YOUR host resets here — a reset keyed on
|
|
132
|
-
* the handshake races anything the page sends before it, and in React that is structural rather
|
|
133
|
-
* than a mistake, because CHILD effects run before PARENT effects.
|
|
115
|
+
* ⚠ Any per-page state YOUR host resets on this handshake races whatever the page sent before it,
|
|
116
|
+
* and in React that is structural rather than bad luck: CHILD effects run before PARENT effects.
|
|
134
117
|
*
|
|
135
|
-
* The returned promise REJECTS on a failed handshake (disposed bridge, timeout). Handle it —
|
|
118
|
+
* ⚠ The returned promise REJECTS on a failed handshake (disposed bridge, timeout). Handle it —
|
|
136
119
|
* `void bridge.notifyReady()` turns that into an unhandled rejection, which in a WebView2 page is
|
|
137
120
|
* a silent console error.
|
|
138
121
|
*/
|
|
139
122
|
notifyReady<TPayload = unknown>(payload?: TPayload): Promise<ShellInfo | undefined>;
|
|
140
123
|
/**
|
|
141
124
|
* What the host said it was during {@link notifyReady} — undefined before the handshake, or when
|
|
142
|
-
* the host advertised nothing. Cached so components can read it synchronously while rendering
|
|
143
|
-
*
|
|
125
|
+
* the host advertised nothing. Cached so components can read it synchronously while rendering; a
|
|
126
|
+
* capability learned after layout is a visible flash.
|
|
144
127
|
*/
|
|
145
128
|
get shell(): ShellInfo | undefined;
|
|
146
129
|
/** Reject everything in flight and detach from the transport. */
|