@capgo/inappbrowser 8.2.0 → 8.3.0-alpha.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 +110 -28
- package/android/src/main/assets/proxy-bridge.js +197 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +29 -26
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +5 -7
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/ProxyBridge.java +60 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +2 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +262 -165
- package/dist/docs.json +150 -3
- package/dist/esm/definitions.d.ts +65 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +20 -2
- package/dist/esm/index.js +79 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +4 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +83 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +83 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +64 -1
- package/ios/Sources/InAppBrowserPlugin/ProxySchemeHandler.swift +257 -0
- package/ios/Sources/InAppBrowserPlugin/WKWebView+SchemeHandling.swift +53 -0
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +18 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
# @capgo/inappbrowser
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
<a href="https://capgo.app/">
|
|
4
|
+
<img
|
|
5
|
+
src="https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png"
|
|
6
|
+
alt="Capgo - Instant updates for capacitor"
|
|
7
|
+
/>
|
|
8
|
+
</a>
|
|
3
9
|
|
|
4
10
|
<div align="center">
|
|
5
|
-
<h2
|
|
6
|
-
|
|
11
|
+
<h2>
|
|
12
|
+
<a href="https://capgo.app/?ref=plugin_inappbrowser"> ➡️ Get Instant updates for your App with Capgo</a>
|
|
13
|
+
</h2>
|
|
14
|
+
<h2>
|
|
15
|
+
<a href="https://capgo.app/consulting/?ref=plugin_inappbrowser">
|
|
16
|
+
{' '}
|
|
17
|
+
Missing a feature? We’ll build the plugin for you 💪
|
|
18
|
+
</a>
|
|
19
|
+
</h2>
|
|
7
20
|
</div>
|
|
8
21
|
|
|
9
22
|
Capacitor plugin in app browser with urlChangeEvent, two way communication, camera and microphone usage, etc.
|
|
@@ -30,10 +43,10 @@ The most complete doc is available here: https://capgo.app/docs/plugins/inappbro
|
|
|
30
43
|
|
|
31
44
|
| Plugin version | Capacitor compatibility | Maintained |
|
|
32
45
|
| -------------- | ----------------------- | ---------- |
|
|
33
|
-
| v8.\*.\* | v8.\*.\* | ✅
|
|
34
|
-
| v7.\*.\* | v7.\*.\* | On demand
|
|
35
|
-
| v6.\*.\* | v6.\*.\* | ❌
|
|
36
|
-
| v5.\*.\* | v5.\*.\* | ❌
|
|
46
|
+
| v8.\*.\* | v8.\*.\* | ✅ |
|
|
47
|
+
| v7.\*.\* | v7.\*.\* | On demand |
|
|
48
|
+
| v6.\*.\* | v6.\*.\* | ❌ |
|
|
49
|
+
| v5.\*.\* | v5.\*.\* | ❌ |
|
|
37
50
|
|
|
38
51
|
> **Note:** The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.
|
|
39
52
|
|
|
@@ -43,12 +56,13 @@ The most complete doc is available here: https://capgo.app/docs/plugins/inappbro
|
|
|
43
56
|
npm install @capgo/inappbrowser
|
|
44
57
|
npx cap sync
|
|
45
58
|
```
|
|
59
|
+
|
|
46
60
|
## Usage
|
|
47
61
|
|
|
48
62
|
```js
|
|
49
|
-
import { InAppBrowser } from '@capgo/inappbrowser'
|
|
63
|
+
import { InAppBrowser } from '@capgo/inappbrowser';
|
|
50
64
|
|
|
51
|
-
InAppBrowser.open({ url:
|
|
65
|
+
InAppBrowser.open({ url: 'YOUR_URL' });
|
|
52
66
|
```
|
|
53
67
|
|
|
54
68
|
### Customize Chrome Custom Tab Appearance (Android)
|
|
@@ -77,15 +91,15 @@ All CCT options are Android-only and safely ignored on iOS. See [`OpenOptions`](
|
|
|
77
91
|
By default, the webview opens in fullscreen. You can set custom dimensions to control the size and position:
|
|
78
92
|
|
|
79
93
|
```js
|
|
80
|
-
import { InAppBrowser } from '@capgo/inappbrowser'
|
|
94
|
+
import { InAppBrowser } from '@capgo/inappbrowser';
|
|
81
95
|
|
|
82
96
|
// Open with custom dimensions (400x600 at position 50,100)
|
|
83
97
|
const { id } = await InAppBrowser.openWebView({
|
|
84
|
-
url:
|
|
98
|
+
url: 'YOUR_URL',
|
|
85
99
|
width: 400,
|
|
86
100
|
height: 600,
|
|
87
101
|
x: 50,
|
|
88
|
-
y: 100
|
|
102
|
+
y: 100,
|
|
89
103
|
});
|
|
90
104
|
|
|
91
105
|
// Update dimensions at runtime
|
|
@@ -94,7 +108,7 @@ InAppBrowser.updateDimensions({
|
|
|
94
108
|
width: 500,
|
|
95
109
|
height: 700,
|
|
96
110
|
x: 100,
|
|
97
|
-
y: 150
|
|
111
|
+
y: 150,
|
|
98
112
|
});
|
|
99
113
|
```
|
|
100
114
|
|
|
@@ -105,11 +119,11 @@ InAppBrowser.updateDimensions({
|
|
|
105
119
|
To create a webView with a 20px bottom margin (safe margin area outside the browser):
|
|
106
120
|
|
|
107
121
|
```js
|
|
108
|
-
import { InAppBrowser } from '@capgo/inappbrowser'
|
|
122
|
+
import { InAppBrowser } from '@capgo/inappbrowser';
|
|
109
123
|
|
|
110
124
|
InAppBrowser.openWebView({
|
|
111
|
-
url:
|
|
112
|
-
enabledSafeBottomMargin: true
|
|
125
|
+
url: 'YOUR_URL',
|
|
126
|
+
enabledSafeBottomMargin: true,
|
|
113
127
|
});
|
|
114
128
|
```
|
|
115
129
|
|
|
@@ -120,15 +134,16 @@ Web platform is not supported. Use `window.open` instead.
|
|
|
120
134
|
To open the webview in true full screen mode (content extends behind the status bar), set `enabledSafeTopMargin` to `false`:
|
|
121
135
|
|
|
122
136
|
```js
|
|
123
|
-
import { InAppBrowser } from '@capgo/inappbrowser'
|
|
137
|
+
import { InAppBrowser } from '@capgo/inappbrowser';
|
|
124
138
|
|
|
125
139
|
InAppBrowser.openWebView({
|
|
126
|
-
url:
|
|
127
|
-
enabledSafeTopMargin: false
|
|
140
|
+
url: 'YOUR_URL',
|
|
141
|
+
enabledSafeTopMargin: false, // Disables safe area at top, allows full screen
|
|
128
142
|
});
|
|
129
143
|
```
|
|
130
144
|
|
|
131
145
|
This option works independently of the toolbar type:
|
|
146
|
+
|
|
132
147
|
- **iOS**: The webview extends behind the status bar, providing true edge-to-edge content
|
|
133
148
|
- **Android**: The top margin is disabled, allowing content to fill the entire screen
|
|
134
149
|
|
|
@@ -214,16 +229,16 @@ With this plugin you can send events from the main app to the inappbrowser and v
|
|
|
214
229
|
#### Main app to inappbrowser, detail object is mendatory
|
|
215
230
|
|
|
216
231
|
```js
|
|
217
|
-
const { id } = await InAppBrowser.openWebView({ url:
|
|
218
|
-
InAppBrowser.postMessage({ id, detail: { message:
|
|
232
|
+
const { id } = await InAppBrowser.openWebView({ url: 'YOUR_URL' });
|
|
233
|
+
InAppBrowser.postMessage({ id, detail: { message: 'myMessage' } });
|
|
219
234
|
// Or broadcast to all open webviews
|
|
220
|
-
InAppBrowser.postMessage({ detail: { message:
|
|
235
|
+
InAppBrowser.postMessage({ detail: { message: 'broadcast' } });
|
|
221
236
|
```
|
|
222
237
|
|
|
223
238
|
#### Receive event from native in the inappbrowser
|
|
224
239
|
|
|
225
240
|
```js
|
|
226
|
-
window.addEventListener(
|
|
241
|
+
window.addEventListener('messageFromNative', (event) => {
|
|
227
242
|
console.log(event);
|
|
228
243
|
});
|
|
229
244
|
```
|
|
@@ -231,13 +246,13 @@ window.addEventListener("messageFromNative", (event) => {
|
|
|
231
246
|
#### Send event from inappbrowser to main app, detail object is mendatory
|
|
232
247
|
|
|
233
248
|
```js
|
|
234
|
-
window.mobileApp.postMessage({ detail: { message:
|
|
249
|
+
window.mobileApp.postMessage({ detail: { message: 'myMessage' } });
|
|
235
250
|
```
|
|
236
251
|
|
|
237
252
|
#### Receive event from inappbrowser in the main app
|
|
238
253
|
|
|
239
254
|
```js
|
|
240
|
-
InAppBrowser.addListener(
|
|
255
|
+
InAppBrowser.addListener('messageFromWebview', (event) => {
|
|
241
256
|
console.log(event.id, event.detail);
|
|
242
257
|
});
|
|
243
258
|
```
|
|
@@ -272,6 +287,8 @@ window.mobileApp.close();
|
|
|
272
287
|
* [`addListener('messageFromWebview', ...)`](#addlistenermessagefromwebview-)
|
|
273
288
|
* [`addListener('browserPageLoaded', ...)`](#addlistenerbrowserpageloaded-)
|
|
274
289
|
* [`addListener('pageLoadError', ...)`](#addlistenerpageloaderror-)
|
|
290
|
+
* [`addListener('proxyRequest', ...)`](#addlistenerproxyrequest-)
|
|
291
|
+
* [`handleProxyRequest(...)`](#handleproxyrequest)
|
|
275
292
|
* [`removeAllListeners()`](#removealllisteners)
|
|
276
293
|
* [`reload(...)`](#reload)
|
|
277
294
|
* [`updateDimensions(...)`](#updatedimensions)
|
|
@@ -656,6 +673,45 @@ Will be triggered when page load error
|
|
|
656
673
|
--------------------
|
|
657
674
|
|
|
658
675
|
|
|
676
|
+
### addListener('proxyRequest', ...)
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
addListener(eventName: 'proxyRequest', listenerFunc: (event: ProxyRequest) => void) => Promise<PluginListenerHandle>
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Listen for proxied requests from the in-app browser webview.
|
|
683
|
+
Use addProxyHandler() wrapper instead of calling this directly.
|
|
684
|
+
|
|
685
|
+
| Param | Type |
|
|
686
|
+
| ------------------ | ------------------------------------------------------------------------- |
|
|
687
|
+
| **`eventName`** | <code>'proxyRequest'</code> |
|
|
688
|
+
| **`listenerFunc`** | <code>(event: <a href="#proxyrequest">ProxyRequest</a>) => void</code> |
|
|
689
|
+
|
|
690
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
691
|
+
|
|
692
|
+
**Since:** 9.0.0
|
|
693
|
+
|
|
694
|
+
--------------------
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
### handleProxyRequest(...)
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
handleProxyRequest(options: { requestId: string; response: ProxyResponse | null; webviewId?: string; }) => Promise<void>
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
Internal method: sends a proxied response back to native.
|
|
704
|
+
Called by addProxyHandler() wrapper — not intended for direct use.
|
|
705
|
+
|
|
706
|
+
| Param | Type |
|
|
707
|
+
| ------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
708
|
+
| **`options`** | <code>{ requestId: string; response: <a href="#proxyresponse">ProxyResponse</a> \| null; webviewId?: string; }</code> |
|
|
709
|
+
|
|
710
|
+
**Since:** 9.0.0
|
|
711
|
+
|
|
712
|
+
--------------------
|
|
713
|
+
|
|
714
|
+
|
|
659
715
|
### removeAllListeners()
|
|
660
716
|
|
|
661
717
|
```typescript
|
|
@@ -848,7 +904,7 @@ And in the AndroidManifest.xml file:
|
|
|
848
904
|
| **`ignoreUntrustedSSLError`** | <code>boolean</code> | ignoreUntrustedSSLError: if true, the webview will ignore untrusted SSL errors allowing the user to view the website. | <code>false</code> | 6.1.0 |
|
|
849
905
|
| **`preShowScript`** | <code>string</code> | preShowScript: if isPresentAfterPageLoad is true and this variable is set the plugin will inject a script before showing the browser. This script will be run in an async context. The plugin will wait for the script to finish (max 10 seconds) | | 6.6.0 |
|
|
850
906
|
| **`preShowScriptInjectionTime`** | <code>'documentStart' \| 'pageLoad'</code> | preShowScriptInjectionTime: controls when the preShowScript is injected. - "documentStart": injects before any page JavaScript runs (good for polyfills like Firebase) - "pageLoad": injects after page load (default, original behavior) | <code>"pageLoad"</code> | 7.26.0 |
|
|
851
|
-
| **`proxyRequests`** | <code>
|
|
907
|
+
| **`proxyRequests`** | <code>boolean</code> | When true, all HTTP/HTTPS requests from the webview are sent to the proxy handler registered via addProxyHandler(). The handler can return a custom Response or null for pass-through. | | 9.0.0 |
|
|
852
908
|
| **`buttonNearDone`** | <code>{ ios: { iconType: 'sf-symbol' \| 'asset'; icon: string; }; android: { iconType: 'asset' \| 'vector'; icon: string; width?: number; height?: number; }; }</code> | buttonNearDone allows for a creation of a custom button near the done/close button. The button is only shown when toolbarType is not "activity", "navigation", or "blank". For Android: - iconType must be "asset" - icon path should be in the public folder (e.g. "monkey.svg") - width and height are optional, defaults to 48dp - button is positioned at the end of toolbar with 8dp margin For iOS: - iconType can be "sf-symbol" or "asset" - for sf-symbol, icon should be the symbol name - for asset, icon should be the asset name | | 6.7.0 |
|
|
853
909
|
| **`textZoom`** | <code>number</code> | textZoom: sets the text zoom of the page in percent. Allows users to increase or decrease the text size for better readability. | <code>100</code> | 7.6.0 |
|
|
854
910
|
| **`preventDeeplink`** | <code>boolean</code> | preventDeeplink: if true, the deeplink will not be opened, if false the deeplink will be opened when clicked on the link. on IOS each schema need to be added to info.plist file under LSApplicationQueriesSchemes when false to make it work. | <code>false</code> | 0.1.0 |
|
|
@@ -911,6 +967,31 @@ And in the AndroidManifest.xml file:
|
|
|
911
967
|
| **`url`** | <code>string</code> | Emit when a button is clicked. | 0.0.1 |
|
|
912
968
|
|
|
913
969
|
|
|
970
|
+
#### ProxyRequest
|
|
971
|
+
|
|
972
|
+
Represents an intercepted HTTP request from the in-app browser webview.
|
|
973
|
+
|
|
974
|
+
| Prop | Type | Description |
|
|
975
|
+
| --------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
976
|
+
| **`requestId`** | <code>string</code> | Unique identifier for this request, used to match responses |
|
|
977
|
+
| **`url`** | <code>string</code> | The full URL being requested |
|
|
978
|
+
| **`method`** | <code>string</code> | HTTP method (GET, POST, PUT, DELETE, etc.) |
|
|
979
|
+
| **`headers`** | <code><a href="#record">Record</a><string, string></code> | Request headers as key-value pairs |
|
|
980
|
+
| **`body`** | <code>string \| null</code> | Base64-encoded request body, or null if no body. On Android, requests from HTML elements (img, script, link, iframe) are intercepted natively and do not have access to the request body — this field will be empty for those requests. Requests from fetch() and XMLHttpRequest include the full body. |
|
|
981
|
+
| **`webviewId`** | <code>string</code> | ID of the webview that made this request |
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
#### ProxyResponse
|
|
985
|
+
|
|
986
|
+
Response to send back for a proxied request.
|
|
987
|
+
|
|
988
|
+
| Prop | Type | Description |
|
|
989
|
+
| ------------- | --------------------------------------------------------------- | ----------------------------------- |
|
|
990
|
+
| **`body`** | <code>string</code> | Base64-encoded response body |
|
|
991
|
+
| **`status`** | <code>number</code> | HTTP status code |
|
|
992
|
+
| **`headers`** | <code><a href="#record">Record</a><string, string></code> | Response headers as key-value pairs |
|
|
993
|
+
|
|
994
|
+
|
|
914
995
|
#### DimensionOptions
|
|
915
996
|
|
|
916
997
|
| Prop | Type | Description |
|
|
@@ -1024,5 +1105,6 @@ Construct a type with a set of properties K of type T
|
|
|
1024
1105
|
</docgen-api>
|
|
1025
1106
|
|
|
1026
1107
|
**Credits**
|
|
1027
|
-
|
|
1028
|
-
|
|
1108
|
+
|
|
1109
|
+
- [WKWebViewController](https://github.com/Meniny/WKWebViewController) - for iOS
|
|
1110
|
+
- [CapBrowser](https://github.com/gadapa-rakesh/CapBrowser) - For the base in capacitor v2
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
var __async = (__this, __arguments, generator) => {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
var fulfilled = (value) => {
|
|
6
|
+
try {
|
|
7
|
+
step(generator.next(value));
|
|
8
|
+
} catch (e) {
|
|
9
|
+
reject(e);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var rejected = (value) => {
|
|
13
|
+
try {
|
|
14
|
+
step(generator.throw(value));
|
|
15
|
+
} catch (e) {
|
|
16
|
+
reject(e);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
20
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/proxy-bridge.ts
|
|
25
|
+
(function() {
|
|
26
|
+
const proxyBridge = window.__capgoProxy;
|
|
27
|
+
if (!proxyBridge) return;
|
|
28
|
+
const accessToken = "___CAPGO_PROXY_TOKEN___";
|
|
29
|
+
let requestCounter = 0;
|
|
30
|
+
function generateRequestId() {
|
|
31
|
+
return "pr_" + Date.now() + "_" + requestCounter++;
|
|
32
|
+
}
|
|
33
|
+
function arrayBufferToBase64(buffer) {
|
|
34
|
+
const bytes = new Uint8Array(buffer);
|
|
35
|
+
let binary = "";
|
|
36
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
37
|
+
binary += String.fromCharCode(bytes[i]);
|
|
38
|
+
}
|
|
39
|
+
return btoa(binary);
|
|
40
|
+
}
|
|
41
|
+
function stringToBase64(str) {
|
|
42
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
43
|
+
}
|
|
44
|
+
function resolveUrl(url) {
|
|
45
|
+
if (url && !url.match(/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//)) {
|
|
46
|
+
try {
|
|
47
|
+
return new URL(url, window.location.href).href;
|
|
48
|
+
} catch (_e) {
|
|
49
|
+
return url;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return url;
|
|
53
|
+
}
|
|
54
|
+
function bodyToBase64(body) {
|
|
55
|
+
return __async(this, null, function* () {
|
|
56
|
+
if (body === null || body === void 0) return null;
|
|
57
|
+
if (typeof body === "string") return stringToBase64(body);
|
|
58
|
+
if (body instanceof ArrayBuffer) return arrayBufferToBase64(body);
|
|
59
|
+
if (ArrayBuffer.isView(body))
|
|
60
|
+
return arrayBufferToBase64(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
|
|
61
|
+
if (body instanceof Blob) {
|
|
62
|
+
const ab = yield body.arrayBuffer();
|
|
63
|
+
return arrayBufferToBase64(ab);
|
|
64
|
+
}
|
|
65
|
+
if (body instanceof FormData) {
|
|
66
|
+
const ab = yield new Response(body).arrayBuffer();
|
|
67
|
+
return arrayBufferToBase64(ab);
|
|
68
|
+
}
|
|
69
|
+
if (body instanceof URLSearchParams) {
|
|
70
|
+
return stringToBase64(body.toString());
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const originalFetch = window.fetch;
|
|
76
|
+
window.fetch = function(input, init) {
|
|
77
|
+
return __async(this, null, function* () {
|
|
78
|
+
const requestId = generateRequestId();
|
|
79
|
+
let url;
|
|
80
|
+
let method = "GET";
|
|
81
|
+
const headers = {};
|
|
82
|
+
let body = null;
|
|
83
|
+
if (input instanceof Request) {
|
|
84
|
+
url = input.url;
|
|
85
|
+
method = input.method;
|
|
86
|
+
input.headers.forEach((v, k) => {
|
|
87
|
+
headers[k] = v;
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
const cloned = input.clone();
|
|
91
|
+
const ab = yield cloned.arrayBuffer();
|
|
92
|
+
if (ab.byteLength > 0) {
|
|
93
|
+
body = ab;
|
|
94
|
+
}
|
|
95
|
+
} catch (_e) {
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
url = input instanceof URL ? input.toString() : input;
|
|
99
|
+
}
|
|
100
|
+
url = resolveUrl(url);
|
|
101
|
+
if (init) {
|
|
102
|
+
if (init.method) method = init.method;
|
|
103
|
+
if (init.headers) {
|
|
104
|
+
const h = new Headers(init.headers);
|
|
105
|
+
h.forEach((v, k) => {
|
|
106
|
+
headers[k] = v;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (init.body !== void 0) body = init.body;
|
|
110
|
+
}
|
|
111
|
+
if (body instanceof FormData) {
|
|
112
|
+
const encoded = new Response(body);
|
|
113
|
+
const ct = encoded.headers.get("content-type");
|
|
114
|
+
if (ct) {
|
|
115
|
+
Object.keys(headers).forEach((k) => {
|
|
116
|
+
if (k.toLowerCase() === "content-type") delete headers[k];
|
|
117
|
+
});
|
|
118
|
+
headers["content-type"] = ct;
|
|
119
|
+
}
|
|
120
|
+
body = yield encoded.arrayBuffer();
|
|
121
|
+
}
|
|
122
|
+
const base64Body = yield bodyToBase64(body);
|
|
123
|
+
proxyBridge.storeRequest(accessToken, requestId, method, JSON.stringify(headers), base64Body || "");
|
|
124
|
+
const proxyUrl = "/_capgo_proxy_?u=" + encodeURIComponent(url) + "&rid=" + requestId;
|
|
125
|
+
return originalFetch.call(window, proxyUrl, { method: "GET" });
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const XHROpen = XMLHttpRequest.prototype.open;
|
|
129
|
+
const XHRSend = XMLHttpRequest.prototype.send;
|
|
130
|
+
const XHRSetHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
131
|
+
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
132
|
+
this.__proxyMethod = method;
|
|
133
|
+
this.__proxyUrl = resolveUrl(url instanceof URL ? url.toString() : url);
|
|
134
|
+
this.__proxyHeaders = {};
|
|
135
|
+
return XHROpen.apply(this, [method, url, ...rest]);
|
|
136
|
+
};
|
|
137
|
+
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
|
|
138
|
+
if (this.__proxyHeaders) {
|
|
139
|
+
this.__proxyHeaders[name] = value;
|
|
140
|
+
}
|
|
141
|
+
return XHRSetHeader.call(this, name, value);
|
|
142
|
+
};
|
|
143
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
144
|
+
const xhr = this;
|
|
145
|
+
const requestId = generateRequestId();
|
|
146
|
+
const method = xhr.__proxyMethod || "GET";
|
|
147
|
+
const url = xhr.__proxyUrl || "";
|
|
148
|
+
const headers = xhr.__proxyHeaders || {};
|
|
149
|
+
function completeSend(base64Body) {
|
|
150
|
+
proxyBridge.storeRequest(accessToken, requestId, method, JSON.stringify(headers), base64Body);
|
|
151
|
+
const proxyUrl = "/_capgo_proxy_?u=" + encodeURIComponent(url) + "&rid=" + requestId;
|
|
152
|
+
XHROpen.call(xhr, "GET", proxyUrl, true);
|
|
153
|
+
XHRSend.call(xhr, null);
|
|
154
|
+
}
|
|
155
|
+
if (body === null || body === void 0) {
|
|
156
|
+
completeSend("");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (typeof body === "string") {
|
|
160
|
+
completeSend(stringToBase64(body));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (body instanceof ArrayBuffer) {
|
|
164
|
+
completeSend(arrayBufferToBase64(body));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (ArrayBuffer.isView(body)) {
|
|
168
|
+
completeSend(arrayBufferToBase64(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (body instanceof URLSearchParams) {
|
|
172
|
+
completeSend(stringToBase64(body.toString()));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (body instanceof Blob || body instanceof FormData) {
|
|
176
|
+
const encoded = new Response(body);
|
|
177
|
+
if (body instanceof FormData) {
|
|
178
|
+
const ct = encoded.headers.get("content-type");
|
|
179
|
+
if (ct) {
|
|
180
|
+
Object.keys(headers).forEach((k) => {
|
|
181
|
+
if (k.toLowerCase() === "content-type") delete headers[k];
|
|
182
|
+
});
|
|
183
|
+
headers["content-type"] = ct;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
encoded.arrayBuffer().then((ab) => {
|
|
187
|
+
completeSend(arrayBufferToBase64(ab));
|
|
188
|
+
}).catch((_e) => {
|
|
189
|
+
console.error("[proxy-bridge] Failed to encode Blob/FormData body:", _e);
|
|
190
|
+
completeSend("");
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
completeSend("");
|
|
195
|
+
};
|
|
196
|
+
})();
|
|
197
|
+
})();
|
|
@@ -46,8 +46,6 @@ import java.util.Iterator;
|
|
|
46
46
|
import java.util.List;
|
|
47
47
|
import java.util.Map;
|
|
48
48
|
import java.util.UUID;
|
|
49
|
-
import java.util.regex.Pattern;
|
|
50
|
-
import java.util.regex.PatternSyntaxException;
|
|
51
49
|
import org.json.JSONException;
|
|
52
50
|
import org.json.JSONObject;
|
|
53
51
|
|
|
@@ -62,7 +60,7 @@ import org.json.JSONObject;
|
|
|
62
60
|
)
|
|
63
61
|
public class InAppBrowserPlugin extends Plugin implements WebViewDialog.PermissionHandler {
|
|
64
62
|
|
|
65
|
-
private final String pluginVersion = "8.
|
|
63
|
+
private final String pluginVersion = "8.3.0-alpha.0";
|
|
66
64
|
|
|
67
65
|
public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome"; // Change when in stable
|
|
68
66
|
private CustomTabsClient customTabsClient;
|
|
@@ -577,14 +575,7 @@ public class InAppBrowserPlugin extends Plugin implements WebViewDialog.Permissi
|
|
|
577
575
|
options.setTextZoom(textZoom);
|
|
578
576
|
}
|
|
579
577
|
|
|
580
|
-
|
|
581
|
-
if (proxyRequestsStr != null) {
|
|
582
|
-
try {
|
|
583
|
-
options.setProxyRequestsPattern(Pattern.compile(proxyRequestsStr));
|
|
584
|
-
} catch (PatternSyntaxException e) {
|
|
585
|
-
Log.e("WebViewDialog", String.format("Pattern '%s' is not a valid pattern", proxyRequestsStr));
|
|
586
|
-
}
|
|
587
|
-
}
|
|
578
|
+
options.setProxyRequests(Boolean.TRUE.equals(call.getBoolean("proxyRequests", false)));
|
|
588
579
|
|
|
589
580
|
try {
|
|
590
581
|
// Try to set buttonNearDone if present, with better error handling
|
|
@@ -733,6 +724,22 @@ public class InAppBrowserPlugin extends Plugin implements WebViewDialog.Permissi
|
|
|
733
724
|
notifyListeners("confirmBtnClicked", new JSObject().put("id", webViewId).put("url", url));
|
|
734
725
|
}
|
|
735
726
|
|
|
727
|
+
@Override
|
|
728
|
+
public void proxyRequestEvent(String requestId, String url, String method, String headersJson, String body, String wvId) {
|
|
729
|
+
JSObject data = new JSObject();
|
|
730
|
+
data.put("requestId", requestId);
|
|
731
|
+
data.put("url", url);
|
|
732
|
+
data.put("method", method);
|
|
733
|
+
try {
|
|
734
|
+
data.put("headers", new JSObject(headersJson));
|
|
735
|
+
} catch (Exception e) {
|
|
736
|
+
data.put("headers", new JSObject());
|
|
737
|
+
}
|
|
738
|
+
data.put("body", body);
|
|
739
|
+
data.put("webviewId", wvId);
|
|
740
|
+
notifyListeners("proxyRequest", data);
|
|
741
|
+
}
|
|
742
|
+
|
|
736
743
|
@Override
|
|
737
744
|
public void javascriptCallback(String message) {
|
|
738
745
|
// Handle the message received from JavaScript
|
|
@@ -1070,24 +1077,20 @@ public class InAppBrowserPlugin extends Plugin implements WebViewDialog.Permissi
|
|
|
1070
1077
|
}
|
|
1071
1078
|
|
|
1072
1079
|
@PluginMethod
|
|
1073
|
-
public void
|
|
1080
|
+
public void handleProxyRequest(PluginCall call) {
|
|
1081
|
+
String requestId = call.getString("requestId");
|
|
1082
|
+
if (requestId == null) {
|
|
1083
|
+
call.reject("requestId is required");
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1074
1086
|
String webviewId = call.getString("webviewId");
|
|
1075
1087
|
WebViewDialog webViewDialog = webviewId != null ? webViewDialogs.get(webviewId) : resolveDialog(null);
|
|
1076
|
-
if (webViewDialog
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
if (id == null) {
|
|
1080
|
-
Log.e("InAppBrowserProxy", "CRITICAL ERROR, proxy id = null");
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
if (Boolean.FALSE.equals(ok)) {
|
|
1084
|
-
String result = call.getString("result", "");
|
|
1085
|
-
webViewDialog.handleProxyResultError(result, id);
|
|
1086
|
-
} else {
|
|
1087
|
-
JSONObject object = call.getObject("result");
|
|
1088
|
-
webViewDialog.handleProxyResultOk(object, id);
|
|
1089
|
-
}
|
|
1088
|
+
if (webViewDialog == null) {
|
|
1089
|
+
call.reject("Target WebView not found for proxy request");
|
|
1090
|
+
return;
|
|
1090
1091
|
}
|
|
1092
|
+
JSObject response = call.getObject("response");
|
|
1093
|
+
webViewDialog.handleProxyResponse(requestId, response);
|
|
1091
1094
|
call.resolve();
|
|
1092
1095
|
}
|
|
1093
1096
|
|
|
@@ -9,8 +9,6 @@ import java.io.InputStream;
|
|
|
9
9
|
import java.util.ArrayList;
|
|
10
10
|
import java.util.List;
|
|
11
11
|
import java.util.Objects;
|
|
12
|
-
import java.util.Objects;
|
|
13
|
-
import java.util.regex.Pattern;
|
|
14
12
|
|
|
15
13
|
public class Options {
|
|
16
14
|
|
|
@@ -170,7 +168,7 @@ public class Options {
|
|
|
170
168
|
private boolean ignoreUntrustedSSLError;
|
|
171
169
|
private String preShowScript;
|
|
172
170
|
private String toolbarTextColor;
|
|
173
|
-
private
|
|
171
|
+
private boolean proxyRequests = false;
|
|
174
172
|
private boolean materialPicker = false;
|
|
175
173
|
private int textZoom = 100; // Default text zoom is 100%
|
|
176
174
|
private boolean preventDeeplink = false;
|
|
@@ -262,12 +260,12 @@ public class Options {
|
|
|
262
260
|
this.useTopInset = useTopInset;
|
|
263
261
|
}
|
|
264
262
|
|
|
265
|
-
public
|
|
266
|
-
return
|
|
263
|
+
public boolean getProxyRequests() {
|
|
264
|
+
return proxyRequests;
|
|
267
265
|
}
|
|
268
266
|
|
|
269
|
-
public void
|
|
270
|
-
this.
|
|
267
|
+
public void setProxyRequests(boolean proxyRequests) {
|
|
268
|
+
this.proxyRequests = proxyRequests;
|
|
271
269
|
}
|
|
272
270
|
|
|
273
271
|
public PluginCall getPluginCall() {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package ee.forgr.capacitor_inappbrowser;
|
|
2
|
+
|
|
3
|
+
import android.webkit.JavascriptInterface;
|
|
4
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
5
|
+
|
|
6
|
+
public class ProxyBridge {
|
|
7
|
+
|
|
8
|
+
private static final int MAX_STORED_REQUESTS = 256;
|
|
9
|
+
|
|
10
|
+
public static class StoredRequest {
|
|
11
|
+
|
|
12
|
+
public final String method;
|
|
13
|
+
public final String headersJson;
|
|
14
|
+
public final String base64Body;
|
|
15
|
+
|
|
16
|
+
public StoredRequest(String method, String headersJson, String base64Body) {
|
|
17
|
+
this.method = method != null ? method : "";
|
|
18
|
+
this.headersJson = headersJson != null ? headersJson : "{}";
|
|
19
|
+
this.base64Body = base64Body != null ? base64Body : "";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private final ConcurrentHashMap<String, StoredRequest> storedRequests = new ConcurrentHashMap<>();
|
|
24
|
+
private final String accessToken;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param accessToken Random token generated per webview instance.
|
|
28
|
+
* The injected proxy-bridge script receives this token and must
|
|
29
|
+
* pass it on every storeRequest call. Page JS that doesn't know
|
|
30
|
+
* the token cannot abuse the interface.
|
|
31
|
+
*/
|
|
32
|
+
public ProxyBridge(String accessToken) {
|
|
33
|
+
this.accessToken = accessToken;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@JavascriptInterface
|
|
37
|
+
public void storeRequest(String token, String requestId, String method, String headersJson, String base64Body) {
|
|
38
|
+
if (token == null || !token.equals(accessToken)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (requestId == null || requestId.isEmpty()) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (storedRequests.size() >= MAX_STORED_REQUESTS) {
|
|
45
|
+
// Remove an arbitrary entry to prevent unbounded growth
|
|
46
|
+
var it = storedRequests.keys().asIterator();
|
|
47
|
+
if (it.hasNext()) {
|
|
48
|
+
storedRequests.remove(it.next());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
storedRequests.put(requestId, new StoredRequest(method, headersJson, base64Body));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public StoredRequest getAndRemove(String requestId) {
|
|
55
|
+
if (requestId == null) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return storedRequests.remove(requestId);
|
|
59
|
+
}
|
|
60
|
+
}
|