unified-ble-manager 4.0.0-alpha.21 → 4.0.0-alpha.24
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/CHANGELOG.md +46 -0
- package/MIGRATION_4.0.md +4 -4
- package/README.md +8 -8
- package/RELEASE.md +11 -11
- package/ROADMAP.4.0.md +2 -2
- package/docs/ELECTRON.md +2 -2
- package/docs/EXPO_PLUGIN.md +1 -1
- package/docs/GAPS.4.0.md +1 -1
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/NODE.md +1 -1
- package/docs/PLATFORMS.md +2 -2
- package/docs/WEB.md +1 -1
- package/lib/commonjs/electron/main-binding.js +221 -3
- package/lib/commonjs/electron/main-binding.js.map +1 -1
- package/lib/commonjs/electron/main-router.js +24 -2
- package/lib/commonjs/electron/main-router.js.map +1 -1
- package/lib/commonjs/electron/protocol.js +2 -0
- package/lib/commonjs/electron/protocol.js.map +1 -1
- package/lib/commonjs/electron/renderer.js +53 -10
- package/lib/commonjs/electron/renderer.js.map +1 -1
- package/lib/commonjs/implementation-version.js +1 -1
- package/lib/module/electron/main-binding.js +222 -4
- package/lib/module/electron/main-binding.js.map +1 -1
- package/lib/module/electron/main-router.js +24 -2
- package/lib/module/electron/main-router.js.map +1 -1
- package/lib/module/electron/protocol.js +2 -0
- package/lib/module/electron/protocol.js.map +1 -1
- package/lib/module/electron/renderer.js +54 -11
- package/lib/module/electron/renderer.js.map +1 -1
- package/lib/module/implementation-version.js +1 -1
- package/lib/typescript/commonjs/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/backends/winrt/winrt-provider.d.ts +1 -1
- package/lib/typescript/commonjs/src/electron/main-binding.d.ts +21 -2
- package/lib/typescript/commonjs/src/electron/main-binding.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/main-router.d.ts +2 -2
- package/lib/typescript/commonjs/src/electron/main-router.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/protocol.d.ts +9 -3
- package/lib/typescript/commonjs/src/electron/protocol.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/electron/renderer.d.ts +7 -0
- package/lib/typescript/commonjs/src/electron/renderer.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/implementation-version.d.ts +1 -1
- package/lib/typescript/module/src/backends/bluez/bluez-backend-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/corebluetooth/corebluetooth-identity.d.ts +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-android-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/reactnative/react-native-apple-provider.d.ts +1 -1
- package/lib/typescript/module/src/backends/winrt/winrt-provider.d.ts +1 -1
- package/lib/typescript/module/src/electron/main-binding.d.ts +21 -2
- package/lib/typescript/module/src/electron/main-binding.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/main-router.d.ts +2 -2
- package/lib/typescript/module/src/electron/main-router.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/protocol.d.ts +9 -3
- package/lib/typescript/module/src/electron/protocol.d.ts.map +1 -1
- package/lib/typescript/module/src/electron/renderer.d.ts +7 -0
- package/lib/typescript/module/src/electron/renderer.d.ts.map +1 -1
- package/lib/typescript/module/src/implementation-version.d.ts +1 -1
- package/package.json +1 -1
- package/src/electron/main-binding.ts +318 -13
- package/src/electron/main-router.ts +21 -5
- package/src/electron/protocol.ts +16 -3
- package/src/electron/renderer.ts +64 -12
- package/src/implementation-version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,52 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [4.0.0-alpha.24] - 2026-07-31
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Electron event acknowledgements now preserve normalized contract failures
|
|
14
|
+
across the main/preload/renderer boundary instead of collapsing them into
|
|
15
|
+
generic transport errors.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Made renderer ownership self-healing after an Electron document reload or
|
|
20
|
+
replacement navigation: the exact stale lease is invalidated, its resources
|
|
21
|
+
are quarantined, and a fresh renderer client can bootstrap without reusing
|
|
22
|
+
scans, connections, subscriptions, or pending events from the old generation.
|
|
23
|
+
- Prevented permanent acknowledgement failures, oversized-response rollback
|
|
24
|
+
failures, malformed IPC input, and late renderer cleanup from leaking or
|
|
25
|
+
retrying resources indefinitely.
|
|
26
|
+
- Preserved the final authoritative main-router cleanup receipt when renderer
|
|
27
|
+
destruction follows an earlier ambiguous cleanup failure.
|
|
28
|
+
|
|
29
|
+
## [4.0.0-alpha.23] - 2026-07-31
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Updated the canonical packed-artifact Electron smoke to model the complete
|
|
34
|
+
Electron 43 navigation and load-failure listener surface used by renderer
|
|
35
|
+
ownership tracking.
|
|
36
|
+
|
|
37
|
+
## [4.0.0-alpha.22] - 2026-07-31
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Electron renderer ownership now tracks replacement navigation with Electron's
|
|
42
|
+
structured navigation details and one sender-scoped navigation epoch shared
|
|
43
|
+
by overlapping renderer leases.
|
|
44
|
+
- Added compiler proof that Electron 43 `WebContents` satisfies the public
|
|
45
|
+
main-process binding contract.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Prevented the initial document's late `did-navigate` event from retiring the
|
|
50
|
+
renderer lease that document had just bootstrapped.
|
|
51
|
+
- Quarantined every lease admitted after a replacement navigation starts,
|
|
52
|
+
including Strict Mode overlap, so the committed replacement document cannot
|
|
53
|
+
inherit stale scans, connections, subscriptions, or acknowledgements.
|
|
54
|
+
|
|
9
55
|
## [4.0.0-alpha.21] - 2026-07-31
|
|
10
56
|
|
|
11
57
|
### Added
|
package/MIGRATION_4.0.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Architecture and sequencing authority:** [`docs/UNIFIED_BLE_4.0_IMPLEMENTATION_PLAN.md`](docs/UNIFIED_BLE_4.0_IMPLEMENTATION_PLAN.md)
|
|
8
8
|
|
|
9
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
9
|
+
`unified-ble-manager@4.0.0-alpha.24` is the current public 4.0 prerelease. 4.0
|
|
10
10
|
started as a new package with no released 4.0 consumer baseline. This is an
|
|
11
11
|
adoption guide for a project that chooses to move from another BLE integration,
|
|
12
12
|
not a supported in-place upgrade from `@sfourdrinier/react-native-ble-plx`.
|
|
@@ -18,13 +18,13 @@ Install the exact currently supported package line with your application's
|
|
|
18
18
|
package manager:
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
|
-
pnpm add unified-ble-manager@4.0.0-alpha.
|
|
21
|
+
pnpm add unified-ble-manager@4.0.0-alpha.24
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`next` is the mutable prerelease dist-tag; it currently resolves to alpha.
|
|
24
|
+
`next` is the mutable prerelease dist-tag; it currently resolves to alpha.24.
|
|
25
25
|
Pin the exact version you evaluate, and do not use a bare install or `@latest`
|
|
26
26
|
to select the 4.0 alpha train. The prerelease package is Experimental: no
|
|
27
|
-
current evidence record links alpha.
|
|
27
|
+
current evidence record links alpha.24's package artifact to a hardware-backed
|
|
28
28
|
backend support claim. See [`docs/PLATFORMS.md`](docs/PLATFORMS.md).
|
|
29
29
|
|
|
30
30
|
Use only documented package subpaths. The root export is host-neutral; import
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# unified-ble-manager
|
|
4
4
|
|
|
5
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
5
|
+
`unified-ble-manager@4.0.0-alpha.24` is the current public 4.0 prerelease for
|
|
6
6
|
explicit, bytes-first Bluetooth Low Energy management across React Native, Web,
|
|
7
7
|
Electron, and Node hosts. 4.0 is a new package line with no released 4.0
|
|
8
8
|
consumer baseline; adopting it is an explicit integration, not a source-
|
|
@@ -16,14 +16,14 @@ The controlling architecture and sequencing record is
|
|
|
16
16
|
|
|
17
17
|
## Release status and evidence boundary
|
|
18
18
|
|
|
19
|
-
`v4.0.0-alpha.
|
|
20
|
-
[GitHub Release](https://github.com/sfourdrinier/react-native-ble-plx/releases/tag/v4.0.0-alpha.
|
|
19
|
+
`v4.0.0-alpha.24` is published to npm under the `next` dist-tag and has a
|
|
20
|
+
[GitHub Release](https://github.com/sfourdrinier/react-native-ble-plx/releases/tag/v4.0.0-alpha.24).
|
|
21
21
|
It was published by GitHub Actions trusted publishing with an npm SLSA
|
|
22
22
|
provenance attestation. This proves the package release path, not Bluetooth
|
|
23
23
|
behavior.
|
|
24
24
|
|
|
25
25
|
The package and deterministic contract surface are **Experimental**. No current
|
|
26
|
-
evidence record binds the published alpha.
|
|
26
|
+
evidence record binds the published alpha.24 artifact to a hardware-backed
|
|
27
27
|
backend scenario, so no React Native, Web, Node, or Electron backend is
|
|
28
28
|
Preview, Live Preview, Supported, or Reliability-qualified. Missing hardware
|
|
29
29
|
evidence blocks those labels only; it does not invalidate deterministic or
|
|
@@ -47,10 +47,10 @@ validation substitute.
|
|
|
47
47
|
Pin the exact prerelease you validated. pnpm is used in this repository:
|
|
48
48
|
|
|
49
49
|
```sh
|
|
50
|
-
pnpm add unified-ble-manager@4.0.0-alpha.
|
|
50
|
+
pnpm add unified-ble-manager@4.0.0-alpha.24
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
`next` is the mutable prerelease dist-tag. It currently resolves to alpha.
|
|
53
|
+
`next` is the mutable prerelease dist-tag. It currently resolves to alpha.24,
|
|
54
54
|
but a later alpha can change it without changing your lockfile intent. Do not install
|
|
55
55
|
the bare package name or `@latest` when adopting 4.0 alpha: those do not select
|
|
56
56
|
the 4.0 prerelease train. Use your package manager's exact-version syntax when
|
|
@@ -300,10 +300,10 @@ final version uses npm `latest` and a normal GitHub Release.
|
|
|
300
300
|
Check an installed-release candidate against npm metadata before integrating it:
|
|
301
301
|
|
|
302
302
|
```sh
|
|
303
|
-
npm view unified-ble-manager@4.0.0-alpha.
|
|
303
|
+
npm view unified-ble-manager@4.0.0-alpha.24 version dist-tags dist.integrity dist.attestations _npmUser --json
|
|
304
304
|
```
|
|
305
305
|
|
|
306
|
-
For alpha.
|
|
306
|
+
For alpha.24, the result must identify the exact version, `next` dist-tag,
|
|
307
307
|
package integrity, a SLSA provenance attestation, and GitHub Actions as the npm
|
|
308
308
|
trusted publisher. Cross-check the tag and release notes at the GitHub Release
|
|
309
309
|
linked above. Provenance and integrity do not verify live Bluetooth behavior;
|
package/RELEASE.md
CHANGED
|
@@ -20,18 +20,18 @@ especially its package, evidence, deletion, and Section 31 release gates.
|
|
|
20
20
|
|
|
21
21
|
## Current public prerelease
|
|
22
22
|
|
|
23
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
24
|
-
`v4.0.0-alpha.
|
|
23
|
+
`unified-ble-manager@4.0.0-alpha.24` was published from
|
|
24
|
+
`v4.0.0-alpha.24` by GitHub Actions trusted publishing. The published npm
|
|
25
25
|
metadata identifies GitHub Actions as the trusted publisher and includes an npm
|
|
26
26
|
SLSA provenance attestation. Its
|
|
27
|
-
[GitHub Release](https://github.com/sfourdrinier/react-native-ble-plx/releases/tag/v4.0.0-alpha.
|
|
28
|
-
is a prerelease and its notes are generated from the alpha.
|
|
27
|
+
[GitHub Release](https://github.com/sfourdrinier/react-native-ble-plx/releases/tag/v4.0.0-alpha.24)
|
|
28
|
+
is a prerelease and its notes are generated from the alpha.24 section of
|
|
29
29
|
[`CHANGELOG.md`](CHANGELOG.md).
|
|
30
30
|
|
|
31
31
|
This is an Experimental package release. It proves a public package, tested
|
|
32
32
|
exports, and the workflow's deterministic/package gates; it does not establish
|
|
33
33
|
hardware support for a backend. The repository's current evidence records do
|
|
34
|
-
not bind alpha.
|
|
34
|
+
not bind alpha.24's package artifact to a passed physical-radio scenario. No
|
|
35
35
|
backend is thereby Preview, Live Preview, Supported, or Reliability-qualified.
|
|
36
36
|
See [`docs/PLATFORMS.md`](docs/PLATFORMS.md) and
|
|
37
37
|
[`evidence/v1/README.md`](evidence/v1/README.md).
|
|
@@ -47,7 +47,7 @@ verifying that the pushed `vX.Y.Z` tag exactly matches `package.json`:
|
|
|
47
47
|
|
|
48
48
|
| Version form | npm dist-tag | GitHub Release state | Consumer guidance |
|
|
49
49
|
| --- | --- | --- | --- |
|
|
50
|
-
| Hyphenated SemVer prerelease, such as `4.0.0-alpha.
|
|
50
|
+
| Hyphenated SemVer prerelease, such as `4.0.0-alpha.24` | `next` | prerelease | Pin the exact version for reproducible evaluation; `@next` is mutable. |
|
|
51
51
|
| Final SemVer version, such as `4.0.0` | `latest` | normal release | Use the final version only after its published evidence supports the required host claim. |
|
|
52
52
|
|
|
53
53
|
Do not use a bare install or `@latest` to select a 4.0 alpha. The exact package
|
|
@@ -87,18 +87,18 @@ Check the published version, dist-tag, integrity, attestation, and trusted
|
|
|
87
87
|
publisher from npm:
|
|
88
88
|
|
|
89
89
|
```sh
|
|
90
|
-
npm view unified-ble-manager@4.0.0-alpha.
|
|
90
|
+
npm view unified-ble-manager@4.0.0-alpha.24 version dist-tags dist.integrity dist.attestations _npmUser --json
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
Then cross-check the matching tag and GitHub Release:
|
|
94
94
|
|
|
95
95
|
```sh
|
|
96
|
-
gh release view v4.0.0-alpha.
|
|
96
|
+
gh release view v4.0.0-alpha.24 --repo sfourdrinier/react-native-ble-plx --json tagName,isPrerelease,publishedAt,url
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
For alpha.
|
|
99
|
+
For alpha.24, npm must report the exact version, `next`, integrity, a SLSA
|
|
100
100
|
provenance attestation, and GitHub Actions trusted publisher; GitHub must report
|
|
101
|
-
the matching `v4.0.0-alpha.
|
|
101
|
+
the matching `v4.0.0-alpha.24` tag with `isPrerelease: true`. These checks
|
|
102
102
|
verify release identity and supply chain metadata only. They do not verify BLE
|
|
103
103
|
hardware behavior, platform permissions, browser availability, background
|
|
104
104
|
operation, restoration, reconnect, or reliability.
|
|
@@ -111,7 +111,7 @@ backend, hardware, Live Preview, Supported, or Reliability-qualified claims.
|
|
|
111
111
|
Deterministic fault injection remains useful 4.0 contract proof but is never
|
|
112
112
|
physical-radio proof.
|
|
113
113
|
|
|
114
|
-
At the time of alpha.
|
|
114
|
+
At the time of alpha.24, GitHub private vulnerability reporting is disabled and
|
|
115
115
|
no private reporting channel or supported-version response policy is published.
|
|
116
116
|
Release notes and support material must not claim otherwise. Establish and
|
|
117
117
|
publish that external repository policy before advertising a confidential
|
package/ROADMAP.4.0.md
CHANGED
|
@@ -44,7 +44,7 @@ Applications and vendor libraries own device choice, vendor protocols, product r
|
|
|
44
44
|
|
|
45
45
|
## Published alpha and support claims
|
|
46
46
|
|
|
47
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
47
|
+
`unified-ble-manager@4.0.0-alpha.24` is published under npm's `next` dist-tag
|
|
48
48
|
with GitHub Actions trusted publishing, npm SLSA provenance, and a GitHub
|
|
49
49
|
prerelease. Its host-neutral root and explicit public subpaths are the current
|
|
50
50
|
alpha package contract; users must pin an exact alpha version rather than infer
|
|
@@ -52,7 +52,7 @@ an API from transitional source files or examples. The alpha has no 3.x
|
|
|
52
52
|
compatibility layer.
|
|
53
53
|
|
|
54
54
|
The current package release is Experimental. There is no current evidence record
|
|
55
|
-
linking alpha.
|
|
55
|
+
linking alpha.24's package artifact to a passed physical-radio backend scenario,
|
|
56
56
|
so it makes no Preview-or-higher platform claim. Hardware evidence is required
|
|
57
57
|
only for the corresponding support label; it does not erase package or
|
|
58
58
|
deterministic proof.
|
package/docs/ELECTRON.md
CHANGED
|
@@ -19,10 +19,10 @@ There is no Noble dependency, renderer Web Bluetooth fallback, legacy
|
|
|
19
19
|
`BlePort`, `PortBleManager`, or mock-radio production fallback in these
|
|
20
20
|
entrypoints.
|
|
21
21
|
|
|
22
|
-
The published `4.0.0-alpha.
|
|
22
|
+
The published `4.0.0-alpha.24` Electron surface is Experimental. The release
|
|
23
23
|
workflow's packed Electron smoke is deterministic L1 package/IPC proof, not an
|
|
24
24
|
Electron host, adapter, or peripheral support claim. No current evidence record
|
|
25
|
-
binds alpha.
|
|
25
|
+
binds alpha.24 to a physical Electron radio journey.
|
|
26
26
|
|
|
27
27
|
## Main-process backend selection
|
|
28
28
|
|
package/docs/EXPO_PLUGIN.md
CHANGED
|
@@ -12,7 +12,7 @@ options are exactly the schema implemented in `plugin/src/withBLE.ts`:
|
|
|
12
12
|
|
|
13
13
|
The plugin configuration is part of the Experimental 4.0 alpha package. The
|
|
14
14
|
release workflow validates Expo SDK 57 CNG prebuild and Android assembly, but no
|
|
15
|
-
current evidence record binds alpha.
|
|
15
|
+
current evidence record binds alpha.24 to a physical device, permission,
|
|
16
16
|
background, or restoration scenario. Configuration and compilation therefore do
|
|
17
17
|
not make a live-radio or restoration support claim.
|
|
18
18
|
|
package/docs/GAPS.4.0.md
CHANGED
|
@@ -14,7 +14,7 @@ This file tracks platform code, CI, package, lab, and live-radio evidence. It do
|
|
|
14
14
|
|
|
15
15
|
The pre-4.0 source tree contains a transitional `BleManager`, `BlePort`, `PortBleManager`, Base64 bridge, byte convenience methods, static capability helpers, Noble-era code, and host examples. Any statement below about that source is current-state characterization and migration input. It is not proof that a clean-baseline 4.0 replacement exists.
|
|
16
16
|
|
|
17
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
17
|
+
`unified-ble-manager@4.0.0-alpha.24` is published as an Experimental package,
|
|
18
18
|
but no current record in `evidence/v1/records/` binds that artifact to a passed
|
|
19
19
|
physical-radio backend scenario. The records below are historical L0 context and
|
|
20
20
|
must not be read as Preview-or-higher evidence for the published alpha.
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 4.0 status
|
|
6
6
|
|
|
7
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
7
|
+
`unified-ble-manager@4.0.0-alpha.24` is the current public 4.0 alpha package
|
|
8
8
|
line. It is a clean API line launched with no released 4.0 consumer baseline:
|
|
9
9
|
choose one explicit host entrypoint and build the matching native integration
|
|
10
10
|
before making a Bluetooth claim. It is not a source-compatible rename of the
|
|
@@ -15,13 +15,13 @@ The architecture and implementation sequence are controlled by [`UNIFIED_BLE_4.0
|
|
|
15
15
|
## Install and select a host
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
pnpm add unified-ble-manager@4.0.0-alpha.
|
|
18
|
+
pnpm add unified-ble-manager@4.0.0-alpha.24
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
The alpha train publishes under npm's mutable `next` dist-tag. Pin the exact
|
|
22
22
|
version you validate; do not use a bare install or `@latest` to select 4.0
|
|
23
23
|
alpha. The package is Experimental, and no current evidence record binds the
|
|
24
|
-
published alpha.
|
|
24
|
+
published alpha.24 artifact to a physical-radio backend result. Package builds
|
|
25
25
|
and deterministic tests remain useful proof at their own scope, but do not
|
|
26
26
|
create a platform support label.
|
|
27
27
|
|
package/docs/NODE.md
CHANGED
|
@@ -6,7 +6,7 @@ The root `unified-ble-manager` entrypoint is host-neutral. Node applications
|
|
|
6
6
|
must select an owned backend through an explicit subpath; a package import never
|
|
7
7
|
chooses an adapter, enables a mock, or falls back to Noble.
|
|
8
8
|
|
|
9
|
-
The Node host surfaces in `4.0.0-alpha.
|
|
9
|
+
The Node host surfaces in `4.0.0-alpha.24` are Experimental. No current
|
|
10
10
|
evidence record binds the published alpha package artifact to a physical BlueZ,
|
|
11
11
|
CoreBluetooth, or WinRT scenario, so these entrypoints are not Preview-or-higher
|
|
12
12
|
support claims.
|
package/docs/PLATFORMS.md
CHANGED
|
@@ -8,8 +8,8 @@ This page is an evidence index, not a static 4.0 capability matrix. In 4.0, an a
|
|
|
8
8
|
|
|
9
9
|
## Current alpha maturity
|
|
10
10
|
|
|
11
|
-
`unified-ble-manager@4.0.0-alpha.
|
|
12
|
-
No current evidence record binds the published alpha.
|
|
11
|
+
`unified-ble-manager@4.0.0-alpha.24` is an **Experimental** package release.
|
|
12
|
+
No current evidence record binds the published alpha.24 artifact to a passed
|
|
13
13
|
physical-radio backend scenario. Consequently, no backend below is currently
|
|
14
14
|
Preview, Live Preview, Supported, or Reliability-qualified. This is not a
|
|
15
15
|
static 4.0 capability matrix: the table says what public surface and automated
|
package/docs/WEB.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Web Bluetooth is a first-party 4.0 backend with browser chooser constraints
|
|
10
10
|
represented as a typed feature. It is not continuous scanning in disguise and
|
|
11
|
-
does not grant a static support claim. `unified-ble-manager@4.0.0-alpha.
|
|
11
|
+
does not grant a static support claim. `unified-ble-manager@4.0.0-alpha.24`
|
|
12
12
|
publishes this API under npm's `next` tag, but no current evidence record binds
|
|
13
13
|
that artifact to a browser/adapter/peripheral live-radio scenario. Web is
|
|
14
14
|
therefore Experimental, not Preview-or-higher.
|
|
@@ -30,6 +30,7 @@ const destroyedRendererRetryDelayMilliseconds = 100;
|
|
|
30
30
|
class ElectronMainBleBinding {
|
|
31
31
|
renderers = new Map();
|
|
32
32
|
bootstrapAdmissionTails = new Map();
|
|
33
|
+
navigationStates = new WeakMap();
|
|
33
34
|
installed = false;
|
|
34
35
|
lifecycle = 'active';
|
|
35
36
|
destroyResult = null;
|
|
@@ -42,7 +43,7 @@ class ElectronMainBleBinding {
|
|
|
42
43
|
if (this.installed) {
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
|
-
this.options.port.handle(_protocol.ELECTRON_BLE_IPC_CHANNEL, (event, request) => this.
|
|
46
|
+
this.options.port.handle(_protocol.ELECTRON_BLE_IPC_CHANNEL, (event, request) => this.handleIpcRequest(event, request));
|
|
46
47
|
this.installed = true;
|
|
47
48
|
}
|
|
48
49
|
uninstall() {
|
|
@@ -123,6 +124,12 @@ class ElectronMainBleBinding {
|
|
|
123
124
|
this.completeRendererRelease(rendererLeaseId);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
if (routerCleanup.state === 'released') {
|
|
128
|
+
return {
|
|
129
|
+
state: 'released',
|
|
130
|
+
failures: []
|
|
131
|
+
};
|
|
132
|
+
}
|
|
126
133
|
const failures = [...routerCleanup.failures];
|
|
127
134
|
for (const cleanup of releaseRecords) {
|
|
128
135
|
failures.push(...cleanup.failures);
|
|
@@ -135,6 +142,38 @@ class ElectronMainBleBinding {
|
|
|
135
142
|
failures
|
|
136
143
|
};
|
|
137
144
|
}
|
|
145
|
+
async handleIpcRequest(event, request) {
|
|
146
|
+
try {
|
|
147
|
+
assertElectronBleIpcRequest(request);
|
|
148
|
+
return await this.handle(event, request);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return this.failureResponse(error, request);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
failureResponse(error, request) {
|
|
154
|
+
if (error instanceof _errors.BackendContractError) {
|
|
155
|
+
return {
|
|
156
|
+
kind: 'failure',
|
|
157
|
+
error: error.normalized
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
console.error('[ElectronMainBleBinding] IPC request failed:', {
|
|
161
|
+
functionName: 'ElectronMainBleBinding.handleIpcRequest',
|
|
162
|
+
operation: electronIpcRequestKind(request),
|
|
163
|
+
error
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
kind: 'failure',
|
|
167
|
+
error: (0, _errors.contractError)('platform.failure', 'ipc', 'electron-main-binding.ipc-handler', {
|
|
168
|
+
domain: 'electron-ipc',
|
|
169
|
+
code: 'unexpected-handler-error',
|
|
170
|
+
safeMessage: 'The Electron main process could not complete the BLE request.',
|
|
171
|
+
metadata: {
|
|
172
|
+
requestKind: electronIpcRequestKind(request)
|
|
173
|
+
}
|
|
174
|
+
}).normalized
|
|
175
|
+
};
|
|
176
|
+
}
|
|
138
177
|
async handle(event, request) {
|
|
139
178
|
this.assertActiveLifecycle();
|
|
140
179
|
this.assertMainFrame(event);
|
|
@@ -162,7 +201,19 @@ class ElectronMainBleBinding {
|
|
|
162
201
|
kind: 'event.ack'
|
|
163
202
|
};
|
|
164
203
|
}
|
|
165
|
-
|
|
204
|
+
let response;
|
|
205
|
+
try {
|
|
206
|
+
response = await this.options.router.dispatch(trusted, request);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (isRollbackReleaseRequiredError(error) && bound !== undefined) {
|
|
209
|
+
bound.releaseRequired = true;
|
|
210
|
+
const cleanup = await this.releaseRendererAuthoritatively(rendererLeaseId, bound);
|
|
211
|
+
if (cleanup?.state === 'released') {
|
|
212
|
+
throw (0, _errors.contractError)('ownership.denied', 'ipc', 'electron-main-arbiter.renderer-registration');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
166
217
|
if (response.kind === 'release' && response.cleanup.state === 'released') {
|
|
167
218
|
if (request.kind !== 'release') {
|
|
168
219
|
throw (0, _errors.contractError)('lifecycle.invariant-violation', 'ipc', 'electron-main-binding.release-response');
|
|
@@ -200,6 +251,9 @@ class ElectronMainBleBinding {
|
|
|
200
251
|
this.assertMainFrame(event);
|
|
201
252
|
this.assertTrustedSenderCurrent(event, trusted);
|
|
202
253
|
this.assertBootstrapSender(event.sender, trusted);
|
|
254
|
+
const navigationState = this.navigationState(event.sender);
|
|
255
|
+
const navigationEpochAtAdmission = navigationState.epoch;
|
|
256
|
+
const replacementWasPendingForAdmittedFrame = navigationState.pendingReplacement && navigationState.sourceFrame?.processId === event.processId && navigationState.sourceFrame.routingId === event.frameId;
|
|
203
257
|
const response = await this.options.router.dispatch(trusted, {
|
|
204
258
|
kind: 'bootstrap'
|
|
205
259
|
});
|
|
@@ -212,17 +266,104 @@ class ElectronMainBleBinding {
|
|
|
212
266
|
const destroyedListener = () => {
|
|
213
267
|
this.retireRenderer(rendererLeaseId, renderer, true, 'destroyed');
|
|
214
268
|
};
|
|
269
|
+
let replacementNavigationStarted = replacementWasPendingForAdmittedFrame || navigationState.epoch !== navigationEpochAtAdmission;
|
|
270
|
+
const navigationStartListener = details => {
|
|
271
|
+
if (details.isMainFrame && !details.isSameDocument) {
|
|
272
|
+
if (navigationState.lastStartDetails !== details) {
|
|
273
|
+
if (navigationState.pendingReplacement && navigationState.lastStartDetails !== null) {
|
|
274
|
+
navigationState.supersededTargetUrls.push(navigationState.lastStartDetails.url);
|
|
275
|
+
}
|
|
276
|
+
navigationState.epoch += 1;
|
|
277
|
+
navigationState.pendingReplacement = true;
|
|
278
|
+
navigationState.lastStartDetails = details;
|
|
279
|
+
navigationState.sourceFrame = Object.freeze({
|
|
280
|
+
processId: renderer.frame.processId,
|
|
281
|
+
routingId: renderer.frame.routingId
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
replacementNavigationStarted = true;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const navigationRedirectListener = details => {
|
|
288
|
+
if (details.isMainFrame && !details.isSameDocument && navigationState.pendingReplacement) {
|
|
289
|
+
navigationState.lastStartDetails = details;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
215
292
|
const navigationListener = () => {
|
|
216
|
-
|
|
293
|
+
navigationState.pendingReplacement = false;
|
|
294
|
+
navigationState.lastStartDetails = null;
|
|
295
|
+
navigationState.supersededTargetUrls.length = 0;
|
|
296
|
+
navigationState.unmatchedProvisionalFailures.clear();
|
|
297
|
+
navigationState.lastProvisionalFailureEvent = null;
|
|
298
|
+
navigationState.lastLoadFailureEvent = null;
|
|
299
|
+
navigationState.sourceFrame = null;
|
|
300
|
+
if (replacementNavigationStarted) {
|
|
301
|
+
this.retireRenderer(rendererLeaseId, renderer, false, 'navigation');
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const applyNavigationFailure = (validatedUrl, isMainFrame) => {
|
|
305
|
+
if (!isMainFrame || !navigationState.pendingReplacement) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const supersededTargetIndex = navigationState.supersededTargetUrls.indexOf(validatedUrl);
|
|
309
|
+
if (navigationState.lastStartDetails?.url !== validatedUrl || supersededTargetIndex >= 0) {
|
|
310
|
+
if (supersededTargetIndex >= 0) {
|
|
311
|
+
navigationState.supersededTargetUrls.splice(supersededTargetIndex, 1);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
navigationState.pendingReplacement = false;
|
|
316
|
+
navigationState.lastStartDetails = null;
|
|
317
|
+
navigationState.supersededTargetUrls.length = 0;
|
|
318
|
+
navigationState.unmatchedProvisionalFailures.clear();
|
|
319
|
+
navigationState.sourceFrame = null;
|
|
320
|
+
};
|
|
321
|
+
const navigationProvisionalFailureListener = (failureEvent, errorCode, _errorDescription, validatedUrl, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
322
|
+
if (navigationState.lastProvisionalFailureEvent === failureEvent) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
navigationState.lastProvisionalFailureEvent = failureEvent;
|
|
326
|
+
const fingerprint = navigationFailureFingerprint(errorCode, validatedUrl, isMainFrame, frameProcessId, frameRoutingId);
|
|
327
|
+
const unmatchedCount = navigationState.unmatchedProvisionalFailures.get(fingerprint) ?? 0;
|
|
328
|
+
navigationState.unmatchedProvisionalFailures.set(fingerprint, unmatchedCount + 1);
|
|
329
|
+
applyNavigationFailure(validatedUrl, isMainFrame);
|
|
330
|
+
};
|
|
331
|
+
const navigationFailureListener = (failureEvent, errorCode, _errorDescription, validatedUrl, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
332
|
+
if (navigationState.lastLoadFailureEvent === failureEvent) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
navigationState.lastLoadFailureEvent = failureEvent;
|
|
336
|
+
const fingerprint = navigationFailureFingerprint(errorCode, validatedUrl, isMainFrame, frameProcessId, frameRoutingId);
|
|
337
|
+
const unmatchedCount = navigationState.unmatchedProvisionalFailures.get(fingerprint) ?? 0;
|
|
338
|
+
if (unmatchedCount > 0) {
|
|
339
|
+
// Electron exposes no navigation ID that can distinguish two same-target failures.
|
|
340
|
+
// Consume the possible provisional/load pair without disarming the current navigation;
|
|
341
|
+
// the renderer remains usable and is retired only if a later document actually commits.
|
|
342
|
+
if (unmatchedCount === 1) {
|
|
343
|
+
navigationState.unmatchedProvisionalFailures.delete(fingerprint);
|
|
344
|
+
} else {
|
|
345
|
+
navigationState.unmatchedProvisionalFailures.set(fingerprint, unmatchedCount - 1);
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
applyNavigationFailure(validatedUrl, isMainFrame);
|
|
217
350
|
};
|
|
218
351
|
const renderProcessGoneListener = () => {
|
|
219
352
|
this.retireRenderer(rendererLeaseId, renderer, true, 'render-process-gone');
|
|
220
353
|
};
|
|
221
354
|
renderer.destroyedListener = destroyedListener;
|
|
355
|
+
renderer.navigationStartListener = navigationStartListener;
|
|
356
|
+
renderer.navigationRedirectListener = navigationRedirectListener;
|
|
222
357
|
renderer.navigationListener = navigationListener;
|
|
358
|
+
renderer.navigationFailureListener = navigationFailureListener;
|
|
359
|
+
renderer.navigationProvisionalFailureListener = navigationProvisionalFailureListener;
|
|
223
360
|
renderer.renderProcessGoneListener = renderProcessGoneListener;
|
|
224
361
|
event.sender.once('destroyed', destroyedListener);
|
|
362
|
+
event.sender.on('did-start-navigation', navigationStartListener);
|
|
363
|
+
event.sender.on('did-redirect-navigation', navigationRedirectListener);
|
|
225
364
|
event.sender.on('did-navigate', navigationListener);
|
|
365
|
+
event.sender.on('did-fail-load', navigationFailureListener);
|
|
366
|
+
event.sender.on('did-fail-provisional-load', navigationProvisionalFailureListener);
|
|
226
367
|
event.sender.on('render-process-gone', renderProcessGoneListener);
|
|
227
368
|
try {
|
|
228
369
|
this.assertActiveLifecycle();
|
|
@@ -239,6 +380,24 @@ class ElectronMainBleBinding {
|
|
|
239
380
|
}
|
|
240
381
|
return response;
|
|
241
382
|
}
|
|
383
|
+
navigationState(sender) {
|
|
384
|
+
const existing = this.navigationStates.get(sender);
|
|
385
|
+
if (existing !== undefined) {
|
|
386
|
+
return existing;
|
|
387
|
+
}
|
|
388
|
+
const created = {
|
|
389
|
+
epoch: 0,
|
|
390
|
+
pendingReplacement: false,
|
|
391
|
+
lastStartDetails: null,
|
|
392
|
+
supersededTargetUrls: [],
|
|
393
|
+
unmatchedProvisionalFailures: new Map(),
|
|
394
|
+
lastProvisionalFailureEvent: null,
|
|
395
|
+
lastLoadFailureEvent: null,
|
|
396
|
+
sourceFrame: null
|
|
397
|
+
};
|
|
398
|
+
this.navigationStates.set(sender, created);
|
|
399
|
+
return created;
|
|
400
|
+
}
|
|
242
401
|
assertMainFrame(event) {
|
|
243
402
|
if (event === null || event === undefined || event.sender === null || event.sender === undefined) {
|
|
244
403
|
throw (0, _errors.contractError)('protocol.malformed', 'ipc', 'electron-main-binding.frame-identity');
|
|
@@ -440,12 +599,14 @@ class ElectronMainBleBinding {
|
|
|
440
599
|
if (cleanup.state === 'release-failed') {
|
|
441
600
|
this.scheduleRendererReleaseRetry(rendererLeaseId, renderer);
|
|
442
601
|
}
|
|
602
|
+
return cleanup;
|
|
443
603
|
} catch (error) {
|
|
444
604
|
console.error('[ElectronMainBleBinding] Authoritative renderer release rejected:', {
|
|
445
605
|
rendererLeaseId,
|
|
446
606
|
error
|
|
447
607
|
});
|
|
448
608
|
this.scheduleRendererReleaseRetry(rendererLeaseId, renderer);
|
|
609
|
+
return null;
|
|
449
610
|
}
|
|
450
611
|
}
|
|
451
612
|
scheduleRendererReleaseRetry(rendererLeaseId, renderer) {
|
|
@@ -522,6 +683,22 @@ class ElectronMainBleBinding {
|
|
|
522
683
|
renderer.sender.removeListener('did-navigate', renderer.navigationListener);
|
|
523
684
|
renderer.navigationListener = null;
|
|
524
685
|
}
|
|
686
|
+
if (renderer.navigationStartListener !== null) {
|
|
687
|
+
renderer.sender.removeListener('did-start-navigation', renderer.navigationStartListener);
|
|
688
|
+
renderer.navigationStartListener = null;
|
|
689
|
+
}
|
|
690
|
+
if (renderer.navigationRedirectListener !== null) {
|
|
691
|
+
renderer.sender.removeListener('did-redirect-navigation', renderer.navigationRedirectListener);
|
|
692
|
+
renderer.navigationRedirectListener = null;
|
|
693
|
+
}
|
|
694
|
+
if (renderer.navigationFailureListener !== null) {
|
|
695
|
+
renderer.sender.removeListener('did-fail-load', renderer.navigationFailureListener);
|
|
696
|
+
renderer.navigationFailureListener = null;
|
|
697
|
+
}
|
|
698
|
+
if (renderer.navigationProvisionalFailureListener !== null) {
|
|
699
|
+
renderer.sender.removeListener('did-fail-provisional-load', renderer.navigationProvisionalFailureListener);
|
|
700
|
+
renderer.navigationProvisionalFailureListener = null;
|
|
701
|
+
}
|
|
525
702
|
if (renderer.renderProcessGoneListener !== null) {
|
|
526
703
|
renderer.sender.removeListener('render-process-gone', renderer.renderProcessGoneListener);
|
|
527
704
|
renderer.renderProcessGoneListener = null;
|
|
@@ -529,6 +706,9 @@ class ElectronMainBleBinding {
|
|
|
529
706
|
}
|
|
530
707
|
}
|
|
531
708
|
exports.ElectronMainBleBinding = ElectronMainBleBinding;
|
|
709
|
+
function navigationFailureFingerprint(errorCode, validatedUrl, isMainFrame, frameProcessId, frameRoutingId) {
|
|
710
|
+
return JSON.stringify([errorCode, validatedUrl, isMainFrame, frameProcessId, frameRoutingId]);
|
|
711
|
+
}
|
|
532
712
|
function createBoundRenderer(event, trusted, rendererLease) {
|
|
533
713
|
return {
|
|
534
714
|
rendererLease,
|
|
@@ -551,7 +731,11 @@ function createBoundRenderer(event, trusted, rendererLease) {
|
|
|
551
731
|
retryHandle: null,
|
|
552
732
|
releaseResult: null,
|
|
553
733
|
destroyedListener: null,
|
|
734
|
+
navigationStartListener: null,
|
|
735
|
+
navigationRedirectListener: null,
|
|
554
736
|
navigationListener: null,
|
|
737
|
+
navigationFailureListener: null,
|
|
738
|
+
navigationProvisionalFailureListener: null,
|
|
555
739
|
renderProcessGoneListener: null
|
|
556
740
|
};
|
|
557
741
|
}
|
|
@@ -579,4 +763,38 @@ function assertEventLease(renderer, event) {
|
|
|
579
763
|
throw (0, _errors.contractError)('ownership.denied', 'ipc', 'electron-main-binding.event-lease');
|
|
580
764
|
}
|
|
581
765
|
}
|
|
766
|
+
|
|
767
|
+
/** Narrows the untrusted IPC wire value before any handler dereferences request fields. */
|
|
768
|
+
function assertElectronBleIpcRequest(request) {
|
|
769
|
+
const record = recordValue(request);
|
|
770
|
+
if (record === null) {
|
|
771
|
+
throw (0, _errors.contractError)('protocol.malformed', 'ipc', 'electron-main-binding.request');
|
|
772
|
+
}
|
|
773
|
+
if (record.kind === 'bootstrap') {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (record.kind === 'route' && recordValue(record.envelope) !== null) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (record.kind === 'release' && recordValue(record.rendererLease) !== null) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (record.kind === 'event.ack' && recordValue(record.rendererLease) !== null && typeof record.eventId === 'string') {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
throw (0, _errors.contractError)('protocol.malformed', 'ipc', 'electron-main-binding.request');
|
|
786
|
+
}
|
|
787
|
+
function electronIpcRequestKind(request) {
|
|
788
|
+
const record = recordValue(request);
|
|
789
|
+
return typeof record?.kind === 'string' ? record.kind : 'malformed';
|
|
790
|
+
}
|
|
791
|
+
function recordValue(value) {
|
|
792
|
+
return isRecord(value) ? value : null;
|
|
793
|
+
}
|
|
794
|
+
function isRecord(value) {
|
|
795
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
796
|
+
}
|
|
797
|
+
function isRollbackReleaseRequiredError(error) {
|
|
798
|
+
return error instanceof _errors.BackendContractError && error.normalized.code === 'lifecycle.invalid-state' && error.normalized.operation === 'electron-main-router.rollback-release-required';
|
|
799
|
+
}
|
|
582
800
|
//# sourceMappingURL=main-binding.js.map
|