@condorinst/mobile-bridge 0.17.0 → 0.18.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/CHANGELOG.md +36 -0
- package/README.md +9 -5
- package/dist/index.cjs +45 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -4
- package/dist/index.d.ts +61 -4
- package/dist/index.js +43 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.18.0] — 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Native transport over the `CondorNative` JavaScript channel.** The app is
|
|
15
|
+
moving from `flutter_inappwebview` to the official `webview_flutter`, whose
|
|
16
|
+
only JS → native path is a named channel that accepts a string. When
|
|
17
|
+
`window.CondorNative.postMessage` exists, the SDK uses it and sends the
|
|
18
|
+
`{type, version, payload}` envelope as JSON. It takes precedence over
|
|
19
|
+
`window.flutter_inappwebview`, which keeps working for the transition.
|
|
20
|
+
- **`Bridge.setOutgoingStamp(stamp | null)`.** A single, supported hook that
|
|
21
|
+
transforms every JS → native message (handshake included) right before it is
|
|
22
|
+
sent. It exists so the shell can stamp its nonce. Until now the shell had to
|
|
23
|
+
monkey-patch `flutter_inappwebview.callHandler`, which has no equivalent on a
|
|
24
|
+
channel. Client code should not use it.
|
|
25
|
+
- `NATIVE_CHANNEL_NAME` and the `OutgoingStamp` type are exported.
|
|
26
|
+
- `SandboxVerifyResult.nativeChannelReachable`: the client now also reports
|
|
27
|
+
whether `window.CondorNative` is visible inside the iframe, and `ok` requires
|
|
28
|
+
it to be `false`. Clients built with an older SDK do not send the field; the
|
|
29
|
+
shell treats it as `false`.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Layer 1 override prefix also removes and locks `window.CondorNative`.**
|
|
34
|
+
Measured on a real iPhone (iOS 26.6) and an Android device: the prefix removes
|
|
35
|
+
the channel from the iframe, but the WebView injects the channel into every
|
|
36
|
+
frame, so a child iframe created by the client gets it back, and on iOS
|
|
37
|
+
`window.webkit.messageHandlers` cannot be locked. Layer 1 is defense in depth;
|
|
38
|
+
the guarantee is the nonce validated natively (layer 3), which discarded every
|
|
39
|
+
message sent through those paths.
|
|
40
|
+
|
|
41
|
+
### Deprecated
|
|
42
|
+
|
|
43
|
+
- The `window.flutter_inappwebview` host. It will be removed in a future minor
|
|
44
|
+
once no app ships it.
|
|
45
|
+
|
|
10
46
|
## [0.17.0] — 2026-09-01
|
|
11
47
|
|
|
12
48
|
### Added
|
package/README.md
CHANGED
|
@@ -146,13 +146,13 @@ if (!verify.ok) {
|
|
|
146
146
|
// automatically. Nothing else to do at boot.
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
### Layered defense (propagation of
|
|
149
|
+
### Layered defense (propagation of the native channel into the iframe)
|
|
150
150
|
|
|
151
151
|
| Layer | Where | What it does |
|
|
152
152
|
|---|---|---|
|
|
153
|
-
| **1** | `BridgeShell.bootstrapClientIframe` | `srcdoc` with a prefix that deletes `window.flutter_inappwebview`
|
|
154
|
-
| **2** | `BridgeShell.verifySandbox` + `BridgeClient` handler | Round-trip `postMessage` to attest the iframe state. |
|
|
155
|
-
| **3** | (Native side) | Shell nonce validated natively
|
|
153
|
+
| **1** | `BridgeShell.bootstrapClientIframe` | `srcdoc` with a prefix that deletes `window.CondorNative`, `window.flutter_inappwebview` and `window.CondorBridge` before any client script runs. Defense in depth only: the WebView injects the channel into every frame, so a child iframe created by the client gets it back (and on iOS `window.webkit.messageHandlers` cannot be locked). |
|
|
154
|
+
| **2** | `BridgeShell.verifySandbox` + `BridgeClient` handler | Round-trip `postMessage` to attest the iframe state (`nativeChannelReachable` since 0.18.0). |
|
|
155
|
+
| **3** | (Native side) | Shell nonce, stamped on every outgoing message via `Bridge.setOutgoingStamp`, validated natively. **This is the guarantee.** |
|
|
156
156
|
| **4** | (Fallback if 1+2 fail) | Dual `InAppWebView` — shell and client in separate WebViews. |
|
|
157
157
|
|
|
158
158
|
## Build CLI — `condor-mobile-bridge build`
|
|
@@ -253,7 +253,11 @@ land once there are 2+ versions in the field.
|
|
|
253
253
|
|
|
254
254
|
The package detects automatically:
|
|
255
255
|
|
|
256
|
-
- **Android / iOS** — `window.
|
|
256
|
+
- **Android / iOS** — `window.CondorNative`, a JavaScript channel registered with
|
|
257
|
+
`webview_flutter` (`addJavaScriptChannel('CondorNative')`). Messages are sent as a
|
|
258
|
+
JSON string. Takes precedence when present.
|
|
259
|
+
- **Android / iOS (legacy)** — `window.flutter_inappwebview` (via the `flutter_inappwebview`
|
|
260
|
+
package). Still supported for the transition; will be removed in a future minor.
|
|
257
261
|
- **Browser outside the app** (dev/test): **mock mode** activates automatically.
|
|
258
262
|
|
|
259
263
|
## Mock mode
|
package/dist/index.cjs
CHANGED
|
@@ -33,7 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
BRIDGE_KEYS: () => BRIDGE_KEYS,
|
|
34
34
|
Bridge: () => Bridge,
|
|
35
35
|
BridgeClient: () => BridgeClient,
|
|
36
|
-
BridgeShell: () => BridgeShell
|
|
36
|
+
BridgeShell: () => BridgeShell,
|
|
37
|
+
NATIVE_CHANNEL_NAME: () => NATIVE_CHANNEL_NAME
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(index_exports);
|
|
39
40
|
|
|
@@ -469,8 +470,22 @@ function createMockAdapter() {
|
|
|
469
470
|
}
|
|
470
471
|
|
|
471
472
|
// src/internal/native.ts
|
|
473
|
+
var NATIVE_CHANNEL_NAME = "CondorNative";
|
|
474
|
+
var outgoingStamp = null;
|
|
475
|
+
function setOutgoingStamp(stamp) {
|
|
476
|
+
outgoingStamp = stamp;
|
|
477
|
+
}
|
|
472
478
|
var activeAdapter = null;
|
|
473
|
-
function
|
|
479
|
+
function createChannelAdapter(channel) {
|
|
480
|
+
return {
|
|
481
|
+
isMock: false,
|
|
482
|
+
// Canal único: o `handlerName` não viaja — o nativo roteia por `type`.
|
|
483
|
+
callHandler(_handlerName, payload) {
|
|
484
|
+
channel.postMessage(JSON.stringify(payload));
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function createLegacyAdapter(host) {
|
|
474
489
|
return {
|
|
475
490
|
isMock: false,
|
|
476
491
|
callHandler(handlerName, payload) {
|
|
@@ -482,9 +497,13 @@ function detectAdapter() {
|
|
|
482
497
|
if (typeof window === "undefined") {
|
|
483
498
|
return createMockAdapter();
|
|
484
499
|
}
|
|
500
|
+
const channel = window.CondorNative;
|
|
501
|
+
if (channel && typeof channel.postMessage === "function") {
|
|
502
|
+
return createChannelAdapter(channel);
|
|
503
|
+
}
|
|
485
504
|
const host = window.flutter_inappwebview;
|
|
486
505
|
if (host && typeof host.callHandler === "function") {
|
|
487
|
-
return
|
|
506
|
+
return createLegacyAdapter(host);
|
|
488
507
|
}
|
|
489
508
|
return createMockAdapter();
|
|
490
509
|
}
|
|
@@ -495,7 +514,8 @@ function getActiveAdapter() {
|
|
|
495
514
|
return activeAdapter;
|
|
496
515
|
}
|
|
497
516
|
function callNative(handlerName, payload) {
|
|
498
|
-
|
|
517
|
+
const stamped = outgoingStamp ? outgoingStamp(payload) : payload;
|
|
518
|
+
getActiveAdapter().callHandler(handlerName, stamped);
|
|
499
519
|
}
|
|
500
520
|
|
|
501
521
|
// src/internal/subscribers.ts
|
|
@@ -611,6 +631,9 @@ var Bridge = {
|
|
|
611
631
|
openSettings() {
|
|
612
632
|
this.send("openSettings");
|
|
613
633
|
},
|
|
634
|
+
setOutgoingStamp(stamp) {
|
|
635
|
+
setOutgoingStamp(stamp);
|
|
636
|
+
},
|
|
614
637
|
mockEmit(key, payload) {
|
|
615
638
|
requireMockAdapter().triggerEmit(key, payload ?? {});
|
|
616
639
|
},
|
|
@@ -686,7 +709,8 @@ function onMessage(event) {
|
|
|
686
709
|
postToShell({
|
|
687
710
|
type: "sandbox-verify-response",
|
|
688
711
|
flutterBridgeReachable: typeof w.flutter_inappwebview !== "undefined",
|
|
689
|
-
condorBridgeReachable: typeof w.CondorBridge !== "undefined"
|
|
712
|
+
condorBridgeReachable: typeof w.CondorBridge !== "undefined",
|
|
713
|
+
nativeChannelReachable: typeof w.CondorNative !== "undefined"
|
|
690
714
|
});
|
|
691
715
|
break;
|
|
692
716
|
}
|
|
@@ -812,7 +836,9 @@ var JsErrorMessage = v.object({
|
|
|
812
836
|
var SandboxVerifyResponseMessage = v.object({
|
|
813
837
|
type: v.literal("sandbox-verify-response"),
|
|
814
838
|
flutterBridgeReachable: v.boolean(),
|
|
815
|
-
condorBridgeReachable: v.boolean()
|
|
839
|
+
condorBridgeReachable: v.boolean(),
|
|
840
|
+
// Opcional: clientes com SDK < 0.18.0 não enviam.
|
|
841
|
+
nativeChannelReachable: v.optional(v.boolean())
|
|
816
842
|
});
|
|
817
843
|
var ClientToShellMessage = v.variant("type", [
|
|
818
844
|
SubscribeMessage,
|
|
@@ -867,6 +893,7 @@ var OVERRIDE_PREFIX = `<script>
|
|
|
867
893
|
(function(){
|
|
868
894
|
try { delete window.flutter_inappwebview; } catch (_) {}
|
|
869
895
|
try { delete window.CondorBridge; } catch (_) {}
|
|
896
|
+
try { delete window.CondorNative; } catch (_) {}
|
|
870
897
|
try {
|
|
871
898
|
Object.defineProperty(window, 'flutter_inappwebview', {
|
|
872
899
|
get: function(){ return undefined; },
|
|
@@ -874,6 +901,13 @@ var OVERRIDE_PREFIX = `<script>
|
|
|
874
901
|
configurable: false,
|
|
875
902
|
});
|
|
876
903
|
} catch (_) {}
|
|
904
|
+
try {
|
|
905
|
+
Object.defineProperty(window, 'CondorNative', {
|
|
906
|
+
get: function(){ return undefined; },
|
|
907
|
+
set: function(_){ /* no-op: bloqueia o canal do webview_flutter no iframe */ },
|
|
908
|
+
configurable: false,
|
|
909
|
+
});
|
|
910
|
+
} catch (_) {}
|
|
877
911
|
try {
|
|
878
912
|
Object.defineProperty(window, 'CondorBridge', {
|
|
879
913
|
get: function(){ return undefined; },
|
|
@@ -954,10 +988,12 @@ var BridgeShell = class {
|
|
|
954
988
|
if (pending) {
|
|
955
989
|
clearTimeout(pending.timer);
|
|
956
990
|
this.pendingVerifies.delete(first);
|
|
957
|
-
const
|
|
991
|
+
const nativeChannelReachable = msg.nativeChannelReachable ?? false;
|
|
992
|
+
const ok = !msg.flutterBridgeReachable && !msg.condorBridgeReachable && !nativeChannelReachable;
|
|
958
993
|
pending.resolve({
|
|
959
994
|
flutterBridgeReachable: msg.flutterBridgeReachable,
|
|
960
995
|
condorBridgeReachable: msg.condorBridgeReachable,
|
|
996
|
+
nativeChannelReachable,
|
|
961
997
|
ok
|
|
962
998
|
});
|
|
963
999
|
}
|
|
@@ -1146,6 +1182,7 @@ var BridgeShell = class {
|
|
|
1146
1182
|
BRIDGE_KEYS,
|
|
1147
1183
|
Bridge,
|
|
1148
1184
|
BridgeClient,
|
|
1149
|
-
BridgeShell
|
|
1185
|
+
BridgeShell,
|
|
1186
|
+
NATIVE_CHANNEL_NAME
|
|
1150
1187
|
});
|
|
1151
1188
|
//# sourceMappingURL=index.cjs.map
|