@calvingoh-hexa/capacitor-inappbrowser 6.9.36

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.
Files changed (55) hide show
  1. package/CapgoInappbrowser.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +690 -0
  4. package/android/build.gradle +64 -0
  5. package/android/src/main/AndroidManifest.xml +12 -0
  6. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +741 -0
  7. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +340 -0
  8. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +15 -0
  9. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +1177 -0
  10. package/android/src/main/res/.gitkeep +0 -0
  11. package/android/src/main/res/drawable/arrow_back_disabled.xml +9 -0
  12. package/android/src/main/res/drawable/arrow_back_enabled.xml +9 -0
  13. package/android/src/main/res/drawable/arrow_forward_disabled.xml +9 -0
  14. package/android/src/main/res/drawable/arrow_forward_enabled.xml +9 -0
  15. package/android/src/main/res/drawable/ic_clear_24px.xml +9 -0
  16. package/android/src/main/res/drawable/ic_refresh.xml +9 -0
  17. package/android/src/main/res/layout/activity_browser.xml +22 -0
  18. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  19. package/android/src/main/res/layout/content_browser.xml +16 -0
  20. package/android/src/main/res/layout/tool_bar.xml +72 -0
  21. package/android/src/main/res/values/browser_theme.xml +3 -0
  22. package/android/src/main/res/values/colors.xml +5 -0
  23. package/android/src/main/res/values/dimens.xml +3 -0
  24. package/android/src/main/res/values/strings.xml +11 -0
  25. package/android/src/main/res/values/styles.xml +4 -0
  26. package/dist/docs.json +1865 -0
  27. package/dist/esm/definitions.d.ts +361 -0
  28. package/dist/esm/definitions.js +13 -0
  29. package/dist/esm/definitions.js.map +1 -0
  30. package/dist/esm/index.d.ts +4 -0
  31. package/dist/esm/index.js +7 -0
  32. package/dist/esm/index.js.map +1 -0
  33. package/dist/esm/web.d.ts +19 -0
  34. package/dist/esm/web.js +48 -0
  35. package/dist/esm/web.js.map +1 -0
  36. package/dist/plugin.cjs.js +75 -0
  37. package/dist/plugin.cjs.js.map +1 -0
  38. package/dist/plugin.js +78 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/ios/Plugin/Assets.xcassets/Back.imageset/Back.png +0 -0
  41. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@2x.png +0 -0
  42. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@3x.png +0 -0
  43. package/ios/Plugin/Assets.xcassets/Back.imageset/Contents.json +26 -0
  44. package/ios/Plugin/Assets.xcassets/Contents.json +6 -0
  45. package/ios/Plugin/Assets.xcassets/Forward.imageset/Contents.json +26 -0
  46. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward.png +0 -0
  47. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@2x.png +0 -0
  48. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@3x.png +0 -0
  49. package/ios/Plugin/Enums.swift +65 -0
  50. package/ios/Plugin/InAppBrowserPlugin.h +10 -0
  51. package/ios/Plugin/InAppBrowserPlugin.m +21 -0
  52. package/ios/Plugin/InAppBrowserPlugin.swift +434 -0
  53. package/ios/Plugin/Info.plist +24 -0
  54. package/ios/Plugin/WKWebViewController.swift +1021 -0
  55. package/package.json +83 -0
@@ -0,0 +1,1177 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.app.Activity;
5
+ import android.app.AlertDialog;
6
+ import android.app.Dialog;
7
+ import android.content.ActivityNotFoundException;
8
+ import android.content.Context;
9
+ import android.content.DialogInterface;
10
+ import android.content.Intent;
11
+ import android.content.res.AssetManager;
12
+ import android.graphics.Bitmap;
13
+ import android.graphics.Color;
14
+ import android.graphics.Picture;
15
+ import android.graphics.drawable.PictureDrawable;
16
+ import android.net.Uri;
17
+ import android.net.http.SslError;
18
+ import android.os.Build;
19
+ import android.text.TextUtils;
20
+ import android.util.Base64;
21
+ import android.util.Log;
22
+ import android.view.View;
23
+ import android.view.Window;
24
+ import android.view.WindowManager;
25
+ import android.webkit.HttpAuthHandler;
26
+ import android.webkit.JavascriptInterface;
27
+ import android.webkit.PermissionRequest;
28
+ import android.webkit.SslErrorHandler;
29
+ import android.webkit.ValueCallback;
30
+ import android.webkit.WebChromeClient;
31
+ import android.webkit.WebResourceError;
32
+ import android.webkit.WebResourceRequest;
33
+ import android.webkit.WebResourceResponse;
34
+ import android.webkit.WebView;
35
+ import android.webkit.WebViewClient;
36
+ import android.widget.ImageButton;
37
+ import android.widget.TextView;
38
+ import android.widget.Toast;
39
+ import android.widget.Toolbar;
40
+ import androidx.annotation.RequiresApi;
41
+ import com.caverock.androidsvg.SVG;
42
+ import com.caverock.androidsvg.SVGParseException;
43
+ import com.getcapacitor.JSObject;
44
+ import java.io.ByteArrayInputStream;
45
+ import java.io.IOException;
46
+ import java.io.InputStream;
47
+ import java.net.CookiePolicy;
48
+ import java.net.URI;
49
+ import java.net.URISyntaxException;
50
+ import java.net.URL;
51
+ import java.nio.charset.StandardCharsets;
52
+ import java.util.Arrays;
53
+ import java.util.HashMap;
54
+ import java.util.Iterator;
55
+ import java.util.List;
56
+ import java.util.Map;
57
+ import java.util.Objects;
58
+ import java.util.UUID;
59
+ import java.util.concurrent.ExecutorService;
60
+ import java.util.concurrent.Executors;
61
+ import java.util.concurrent.Semaphore;
62
+ import java.util.concurrent.TimeUnit;
63
+ import java.util.function.Consumer;
64
+ import java.util.regex.Matcher;
65
+ import java.util.regex.Pattern;
66
+ import org.json.JSONException;
67
+ import org.json.JSONObject;
68
+
69
+ public class WebViewDialog extends Dialog {
70
+
71
+ private class ProxiedRequest {
72
+
73
+ private WebResourceResponse response;
74
+ private Semaphore semaphore;
75
+
76
+ public WebResourceResponse getResponse() {
77
+ return response;
78
+ }
79
+
80
+ public ProxiedRequest() {
81
+ this.semaphore = new Semaphore(0);
82
+ this.response = null;
83
+ }
84
+ }
85
+
86
+ private WebView _webView;
87
+ private Toolbar _toolbar;
88
+ private Options _options;
89
+ private Context _context;
90
+ public Activity activity;
91
+ private boolean isInitialized = false;
92
+ private WebView capacitorWebView;
93
+ private HashMap<String, ProxiedRequest> proxiedRequestsHashmap;
94
+
95
+ Semaphore preShowSemaphore = null;
96
+ String preshowError = null;
97
+
98
+ public PermissionRequest currentPermissionRequest;
99
+ public static final int FILE_CHOOSER_REQUEST_CODE = 1000;
100
+ public ValueCallback<Uri> mUploadMessage;
101
+ public ValueCallback<Uri[]> mFilePathCallback;
102
+ ExecutorService executorService = Executors.newFixedThreadPool(1);
103
+
104
+ public interface PermissionHandler {
105
+ void handleCameraPermissionRequest(PermissionRequest request);
106
+
107
+ void handleMicrophonePermissionRequest(PermissionRequest request);
108
+ }
109
+
110
+ private PermissionHandler permissionHandler;
111
+
112
+ public WebViewDialog(
113
+ Context context,
114
+ int theme,
115
+ Options options,
116
+ PermissionHandler permissionHandler,
117
+ WebView capacitorWebView
118
+ ) {
119
+ super(context, theme);
120
+ this._options = options;
121
+ this._context = context;
122
+ this.permissionHandler = permissionHandler;
123
+ this.isInitialized = false;
124
+ this.capacitorWebView = capacitorWebView;
125
+ this.proxiedRequestsHashmap = new HashMap<>();
126
+ }
127
+
128
+ public class JavaScriptInterface {
129
+
130
+ @JavascriptInterface
131
+ public void postMessage(String message) {
132
+ // Handle message from JavaScript
133
+ _options.getCallbacks().javascriptCallback(message);
134
+ }
135
+ }
136
+
137
+ public class PreShowScriptInterface {
138
+
139
+ @JavascriptInterface
140
+ public void error(String error) {
141
+ // Handle message from JavaScript
142
+ if (preShowSemaphore != null) {
143
+ preshowError = error;
144
+ preShowSemaphore.release();
145
+ }
146
+ }
147
+
148
+ @JavascriptInterface
149
+ public void success() {
150
+ // Handle message from JavaScript
151
+ if (preShowSemaphore != null) {
152
+ preShowSemaphore.release();
153
+ }
154
+ }
155
+ }
156
+
157
+ @SuppressLint("SetJavaScriptEnabled")
158
+ public void presentWebView() {
159
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
160
+ setCancelable(true);
161
+ Objects.requireNonNull(getWindow()).setFlags(
162
+ WindowManager.LayoutParams.FLAG_FULLSCREEN,
163
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
164
+ );
165
+ setContentView(R.layout.activity_browser);
166
+ getWindow()
167
+ .setLayout(
168
+ WindowManager.LayoutParams.MATCH_PARENT,
169
+ WindowManager.LayoutParams.MATCH_PARENT
170
+ );
171
+
172
+ this._webView = findViewById(R.id.browser_view);
173
+ _webView.addJavascriptInterface(
174
+ new JavaScriptInterface(),
175
+ "AndroidInterface"
176
+ );
177
+ _webView.addJavascriptInterface(
178
+ new PreShowScriptInterface(),
179
+ "PreShowScriptInterface"
180
+ );
181
+ _webView.getSettings().setJavaScriptEnabled(true);
182
+ _webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
183
+ _webView.getSettings().setDatabaseEnabled(true);
184
+ _webView.getSettings().setDomStorageEnabled(true);
185
+ _webView.getSettings().setAllowFileAccess(true);
186
+ _webView
187
+ .getSettings()
188
+ .setPluginState(android.webkit.WebSettings.PluginState.ON);
189
+ _webView.getSettings().setLoadWithOverviewMode(true);
190
+ _webView.getSettings().setUseWideViewPort(true);
191
+ _webView.getSettings().setAllowFileAccessFromFileURLs(true);
192
+ _webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
193
+ _webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
194
+
195
+ _webView.setWebViewClient(new WebViewClient());
196
+
197
+ _webView.setWebChromeClient(
198
+ new WebChromeClient() {
199
+ // Enable file open dialog
200
+ @Override
201
+ public boolean onShowFileChooser(
202
+ WebView webView,
203
+ ValueCallback<Uri[]> filePathCallback,
204
+ WebChromeClient.FileChooserParams fileChooserParams
205
+ ) {
206
+ openFileChooser(
207
+ filePathCallback,
208
+ fileChooserParams.getAcceptTypes()[0],
209
+ fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE
210
+ );
211
+ return true;
212
+ }
213
+
214
+ // Grant permissions for cam
215
+ @Override
216
+ public void onPermissionRequest(final PermissionRequest request) {
217
+ Log.i(
218
+ "INAPPBROWSER",
219
+ "onPermissionRequest " + Arrays.toString(request.getResources())
220
+ );
221
+ final String[] requestedResources = request.getResources();
222
+ for (String r : requestedResources) {
223
+ Log.i("INAPPBROWSER", "requestedResources " + r);
224
+ if (r.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
225
+ Log.i("INAPPBROWSER", "RESOURCE_VIDEO_CAPTURE req");
226
+ // Store the permission request
227
+ currentPermissionRequest = request;
228
+ // Initiate the permission request through the plugin
229
+ if (permissionHandler != null) {
230
+ permissionHandler.handleCameraPermissionRequest(request);
231
+ }
232
+ return; // Return here to avoid denying the request
233
+ } else if (r.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
234
+ Log.i("INAPPBROWSER", "RESOURCE_AUDIO_CAPTURE req");
235
+ // Store the permission request
236
+ currentPermissionRequest = request;
237
+ // Initiate the permission request through the plugin
238
+ if (permissionHandler != null) {
239
+ permissionHandler.handleMicrophonePermissionRequest(request);
240
+ }
241
+ return; // Return here to avoid denying the request
242
+ }
243
+ }
244
+ // If no matching permission is found, deny the request
245
+ request.deny();
246
+ }
247
+
248
+ @Override
249
+ public void onPermissionRequestCanceled(PermissionRequest request) {
250
+ super.onPermissionRequestCanceled(request);
251
+ Toast.makeText(
252
+ WebViewDialog.this.activity,
253
+ "Permission Denied",
254
+ Toast.LENGTH_SHORT
255
+ ).show();
256
+ // Handle the denied permission
257
+ if (currentPermissionRequest != null) {
258
+ currentPermissionRequest.deny();
259
+ currentPermissionRequest = null;
260
+ }
261
+ }
262
+ }
263
+ );
264
+
265
+ Map<String, String> requestHeaders = new HashMap<>();
266
+ if (_options.getHeaders() != null) {
267
+ Iterator<String> keys = _options.getHeaders().keys();
268
+ while (keys.hasNext()) {
269
+ String key = keys.next();
270
+ if (TextUtils.equals(key.toLowerCase(), "user-agent")) {
271
+ _webView
272
+ .getSettings()
273
+ .setUserAgentString(_options.getHeaders().getString(key));
274
+ } else {
275
+ requestHeaders.put(key, _options.getHeaders().getString(key));
276
+ }
277
+ }
278
+ }
279
+
280
+ _webView.loadUrl(this._options.getUrl(), requestHeaders);
281
+ _webView.requestFocus();
282
+ _webView.requestFocusFromTouch();
283
+
284
+ setupToolbar();
285
+ setWebViewClient();
286
+
287
+ if (!this._options.isPresentAfterPageLoad()) {
288
+ show();
289
+ _options.getPluginCall().resolve();
290
+ }
291
+ }
292
+
293
+ public void postMessageToJS(Object detail) {
294
+ if (_webView != null) {
295
+ try {
296
+ JSONObject jsonObject = new JSONObject();
297
+ jsonObject.put("detail", detail);
298
+ String jsonDetail = jsonObject.toString();
299
+ String script =
300
+ "window.dispatchEvent(new CustomEvent('messageFromNative', " +
301
+ jsonDetail +
302
+ "));";
303
+ _webView.post(() -> _webView.evaluateJavascript(script, null));
304
+ } catch (Exception e) {
305
+ Log.e(
306
+ "postMessageToJS",
307
+ "Error sending message to JS: " + e.getMessage()
308
+ );
309
+ }
310
+ }
311
+ }
312
+
313
+ private void injectJavaScriptInterface() {
314
+ String script =
315
+ "if (!window.mobileApp) { " +
316
+ " window.mobileApp = { " +
317
+ " postMessage: function(message) { " +
318
+ " if (window.AndroidInterface) { " +
319
+ " window.AndroidInterface.postMessage(JSON.stringify(message)); " +
320
+ " } " +
321
+ " } " +
322
+ " }; " +
323
+ "}";
324
+ _webView.evaluateJavascript(script, null);
325
+ }
326
+
327
+ private void injectPreShowScript() {
328
+ // String script =
329
+ // "import('https://unpkg.com/darkreader@4.9.89/darkreader.js').then(() => {DarkReader.enable({ brightness: 100, contrast: 90, sepia: 10 });window.PreLoadScriptInterface.finished()})";
330
+
331
+ if (preShowSemaphore != null) {
332
+ return;
333
+ }
334
+
335
+ String script =
336
+ "async function preShowFunction() {\n" +
337
+ _options.getPreShowScript() +
338
+ '\n' +
339
+ "};\n" +
340
+ "preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('Preshow error', err); window.PreShowScriptInterface.error(JSON.stringify(err, Object.getOwnPropertyNames(err))) })";
341
+
342
+ Log.i(
343
+ "InjectPreShowScript",
344
+ String.format("PreShowScript script:\n%s", script)
345
+ );
346
+
347
+ preShowSemaphore = new Semaphore(0);
348
+ activity.runOnUiThread(
349
+ new Runnable() {
350
+ @Override
351
+ public void run() {
352
+ _webView.evaluateJavascript(script, null);
353
+ }
354
+ }
355
+ );
356
+
357
+ try {
358
+ if (!preShowSemaphore.tryAcquire(10, TimeUnit.SECONDS)) {
359
+ Log.e(
360
+ "InjectPreShowScript",
361
+ "PreShowScript running for over 10 seconds. The plugin will not wait any longer!"
362
+ );
363
+ return;
364
+ }
365
+ if (preshowError != null && !preshowError.isEmpty()) {
366
+ Log.e(
367
+ "InjectPreShowScript",
368
+ "Error within the user-provided preShowFunction: " + preshowError
369
+ );
370
+ }
371
+ } catch (InterruptedException e) {
372
+ Log.e(
373
+ "InjectPreShowScript",
374
+ "Error when calling InjectPreShowScript: " + e.getMessage()
375
+ );
376
+ } finally {
377
+ preShowSemaphore = null;
378
+ preshowError = null;
379
+ }
380
+ }
381
+
382
+ private void openFileChooser(
383
+ ValueCallback<Uri[]> filePathCallback,
384
+ String acceptType,
385
+ boolean isMultiple
386
+ ) {
387
+ mFilePathCallback = filePathCallback;
388
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
389
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
390
+ intent.setType(acceptType); // Default to */*
391
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
392
+ activity.startActivityForResult(
393
+ Intent.createChooser(intent, "Select File"),
394
+ FILE_CHOOSER_REQUEST_CODE
395
+ );
396
+ }
397
+
398
+ public void reload() {
399
+ _webView.reload();
400
+ }
401
+
402
+ public void destroy() {
403
+ _webView.destroy();
404
+ }
405
+
406
+ public String getUrl() {
407
+ // To fix android crash issue
408
+ if (_webView == null) return null;
409
+ return _webView.getUrl();
410
+ }
411
+
412
+ public void executeScript(String script) {
413
+ _webView.evaluateJavascript(script, null);
414
+ }
415
+
416
+ public void setUrl(String url) {
417
+ Map<String, String> requestHeaders = new HashMap<>();
418
+ if (_options.getHeaders() != null) {
419
+ Iterator<String> keys = _options.getHeaders().keys();
420
+ while (keys.hasNext()) {
421
+ String key = keys.next();
422
+ if (TextUtils.equals(key.toLowerCase(), "user-agent")) {
423
+ _webView
424
+ .getSettings()
425
+ .setUserAgentString(_options.getHeaders().getString(key));
426
+ } else {
427
+ requestHeaders.put(key, _options.getHeaders().getString(key));
428
+ }
429
+ }
430
+ }
431
+ _webView.loadUrl(url, requestHeaders);
432
+ }
433
+
434
+ private void setTitle(String newTitleText) {
435
+ TextView textView = (TextView) _toolbar.findViewById(R.id.titleText);
436
+ if (_options.getVisibleTitle()) {
437
+ textView.setText(newTitleText);
438
+ } else {
439
+ textView.setText("");
440
+ }
441
+ }
442
+
443
+ private void setupToolbar() {
444
+ _toolbar = this.findViewById(R.id.tool_bar);
445
+ int color = Color.parseColor("#ffffff");
446
+ try {
447
+ color = Color.parseColor(_options.getToolbarColor());
448
+ } catch (IllegalArgumentException e) {
449
+ // Do nothing
450
+ }
451
+ _toolbar.setBackgroundColor(color);
452
+ _toolbar.findViewById(R.id.backButton).setBackgroundColor(color);
453
+ _toolbar.findViewById(R.id.forwardButton).setBackgroundColor(color);
454
+ _toolbar.findViewById(R.id.closeButton).setBackgroundColor(color);
455
+ _toolbar.findViewById(R.id.reloadButton).setBackgroundColor(color);
456
+
457
+ if (!TextUtils.isEmpty(_options.getTitle())) {
458
+ this.setTitle(_options.getTitle());
459
+ } else {
460
+ try {
461
+ URI uri = new URI(_options.getUrl());
462
+ this.setTitle(uri.getHost());
463
+ } catch (URISyntaxException e) {
464
+ this.setTitle(_options.getTitle());
465
+ }
466
+ }
467
+
468
+ View backButton = _toolbar.findViewById(R.id.backButton);
469
+ backButton.setOnClickListener(
470
+ new View.OnClickListener() {
471
+ @Override
472
+ public void onClick(View view) {
473
+ if (_webView.canGoBack()) {
474
+ _webView.goBack();
475
+ }
476
+ }
477
+ }
478
+ );
479
+
480
+ View forwardButton = _toolbar.findViewById(R.id.forwardButton);
481
+ forwardButton.setOnClickListener(
482
+ new View.OnClickListener() {
483
+ @Override
484
+ public void onClick(View view) {
485
+ if (_webView.canGoForward()) {
486
+ _webView.goForward();
487
+ }
488
+ }
489
+ }
490
+ );
491
+
492
+ View closeButton = _toolbar.findViewById(R.id.closeButton);
493
+ closeButton.setOnClickListener(
494
+ new View.OnClickListener() {
495
+ @Override
496
+ public void onClick(View view) {
497
+ // if closeModal true then display a native modal to check if the user is sure to close the browser
498
+ if (_options.getCloseModal()) {
499
+ new AlertDialog.Builder(_context)
500
+ .setTitle(_options.getCloseModalTitle())
501
+ .setMessage(_options.getCloseModalDescription())
502
+ .setPositiveButton(
503
+ _options.getCloseModalOk(),
504
+ new DialogInterface.OnClickListener() {
505
+ public void onClick(DialogInterface dialog, int which) {
506
+ // Close button clicked, do something
507
+ dismiss();
508
+ _options.getCallbacks().closeEvent(_webView.getUrl());
509
+ _webView.destroy();
510
+ }
511
+ }
512
+ )
513
+ .setNegativeButton(_options.getCloseModalCancel(), null)
514
+ .show();
515
+ } else {
516
+ dismiss();
517
+ _options.getCallbacks().closeEvent(_webView.getUrl());
518
+ _webView.destroy();
519
+ }
520
+ }
521
+ }
522
+ );
523
+
524
+ if (_options.showArrow()) {
525
+ closeButton.setBackgroundResource(R.drawable.arrow_forward_enabled);
526
+ }
527
+
528
+ if (_options.getShowReloadButton()) {
529
+ View reloadButton = _toolbar.findViewById(R.id.reloadButton);
530
+ reloadButton.setVisibility(View.VISIBLE);
531
+ reloadButton.setOnClickListener(
532
+ new View.OnClickListener() {
533
+ @Override
534
+ public void onClick(View view) {
535
+ _webView.reload();
536
+ }
537
+ }
538
+ );
539
+ }
540
+
541
+ if (TextUtils.equals(_options.getToolbarType(), "activity")) {
542
+ _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
543
+ _toolbar.findViewById(R.id.backButton).setVisibility(View.GONE);
544
+ ImageButton buttonNearDoneView = _toolbar.findViewById(
545
+ R.id.buttonNearDone
546
+ );
547
+ buttonNearDoneView.setVisibility(View.GONE);
548
+ //TODO: Add share button functionality
549
+ } else if (TextUtils.equals(_options.getToolbarType(), "navigation")) {
550
+ ImageButton buttonNearDoneView = _toolbar.findViewById(
551
+ R.id.buttonNearDone
552
+ );
553
+ buttonNearDoneView.setVisibility(View.GONE);
554
+ //TODO: Remove share button when implemented
555
+ } else if (TextUtils.equals(_options.getToolbarType(), "blank")) {
556
+ _toolbar.setVisibility(View.GONE);
557
+ } else {
558
+ _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
559
+ _toolbar.findViewById(R.id.backButton).setVisibility(View.GONE);
560
+
561
+ Options.ButtonNearDone buttonNearDone = _options.getButtonNearDone();
562
+ if (buttonNearDone != null) {
563
+ AssetManager assetManager = _context.getAssets();
564
+
565
+ // Open the SVG file from assets
566
+ InputStream inputStream = null;
567
+ try {
568
+ ImageButton buttonNearDoneView = _toolbar.findViewById(
569
+ R.id.buttonNearDone
570
+ );
571
+ buttonNearDoneView.setVisibility(View.VISIBLE);
572
+
573
+ inputStream = assetManager.open(buttonNearDone.getIcon());
574
+
575
+ SVG svg = SVG.getFromInputStream(inputStream);
576
+ Picture picture = svg.renderToPicture(
577
+ buttonNearDone.getWidth(),
578
+ buttonNearDone.getHeight()
579
+ );
580
+ PictureDrawable pictureDrawable = new PictureDrawable(picture);
581
+
582
+ buttonNearDoneView.setImageDrawable(pictureDrawable);
583
+ buttonNearDoneView.setOnClickListener(view ->
584
+ _options.getCallbacks().buttonNearDoneClicked()
585
+ );
586
+ } catch (IOException | SVGParseException e) {
587
+ throw new RuntimeException(e);
588
+ } finally {
589
+ if (inputStream != null) {
590
+ try {
591
+ inputStream.close();
592
+ } catch (IOException e) {
593
+ throw new RuntimeException(e);
594
+ }
595
+ }
596
+ }
597
+ } else {
598
+ ImageButton buttonNearDoneView = _toolbar.findViewById(
599
+ R.id.buttonNearDone
600
+ );
601
+ buttonNearDoneView.setVisibility(View.GONE);
602
+ }
603
+ }
604
+ }
605
+
606
+ public void handleProxyResultError(String result, String id) {
607
+ Log.i(
608
+ "InAppBrowserProxy",
609
+ String.format(
610
+ "handleProxyResultError: %s, ok: %s id: %s",
611
+ result,
612
+ false,
613
+ id
614
+ )
615
+ );
616
+ ProxiedRequest proxiedRequest = proxiedRequestsHashmap.get(id);
617
+ if (proxiedRequest == null) {
618
+ Log.e("InAppBrowserProxy", "proxiedRequest is null");
619
+ return;
620
+ }
621
+ proxiedRequestsHashmap.remove(id);
622
+ proxiedRequest.semaphore.release();
623
+ }
624
+
625
+ public void handleProxyResultOk(JSONObject result, String id) {
626
+ Log.i(
627
+ "InAppBrowserProxy",
628
+ String.format("handleProxyResultOk: %s, ok: %s, id: %s", result, true, id)
629
+ );
630
+ ProxiedRequest proxiedRequest = proxiedRequestsHashmap.get(id);
631
+ if (proxiedRequest == null) {
632
+ Log.e("InAppBrowserProxy", "proxiedRequest is null");
633
+ return;
634
+ }
635
+ proxiedRequestsHashmap.remove(id);
636
+
637
+ if (result == null) {
638
+ proxiedRequest.semaphore.release();
639
+ return;
640
+ }
641
+
642
+ Map<String, String> responseHeaders = new HashMap<>();
643
+ String body;
644
+ int code;
645
+
646
+ try {
647
+ body = result.getString("body");
648
+ code = result.getInt("code");
649
+ JSONObject headers = result.getJSONObject("headers");
650
+ for (Iterator<String> it = headers.keys(); it.hasNext();) {
651
+ String headerName = it.next();
652
+ String header = headers.getString(headerName);
653
+ responseHeaders.put(headerName, header);
654
+ }
655
+ } catch (JSONException e) {
656
+ Log.e("InAppBrowserProxy", "Cannot parse OK result", e);
657
+ return;
658
+ }
659
+
660
+ String contentType = responseHeaders.get("Content-Type");
661
+ if (contentType == null) {
662
+ contentType = responseHeaders.get("content-type");
663
+ }
664
+ if (contentType == null) {
665
+ Log.e("InAppBrowserProxy", "'Content-Type' header is required");
666
+ return;
667
+ }
668
+
669
+ if (!((100 <= code && code <= 299) || (400 <= code && code <= 599))) {
670
+ Log.e(
671
+ "InAppBrowserProxy",
672
+ String.format("Status code %s outside of the allowed range", code)
673
+ );
674
+ return;
675
+ }
676
+
677
+ WebResourceResponse webResourceResponse = new WebResourceResponse(
678
+ contentType,
679
+ "utf-8",
680
+ new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))
681
+ );
682
+
683
+ webResourceResponse.setStatusCodeAndReasonPhrase(
684
+ code,
685
+ getReasonPhrase(code)
686
+ );
687
+ proxiedRequest.response = webResourceResponse;
688
+ proxiedRequest.semaphore.release();
689
+ }
690
+
691
+ private void setWebViewClient() {
692
+ _webView.setWebViewClient(
693
+ new WebViewClient() {
694
+ @Override
695
+ public boolean shouldOverrideUrlLoading(
696
+ WebView view,
697
+ WebResourceRequest request
698
+ ) {
699
+ // HashMap<String, String> map = new HashMap<>();
700
+ // map.put("x-requested-with", null);
701
+ // view.loadUrl(request.getUrl().toString(), map);
702
+ Context context = view.getContext();
703
+ String url = request.getUrl().toString();
704
+
705
+ if (!url.startsWith("https://") && !url.startsWith("http://")) {
706
+ try {
707
+ Intent intent;
708
+ if (url.startsWith("intent://")) {
709
+ intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
710
+ } else {
711
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
712
+ }
713
+
714
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
715
+ context.startActivity(intent);
716
+ return true;
717
+ } catch (ActivityNotFoundException e) {
718
+ // Do nothing
719
+ } catch (URISyntaxException e) {
720
+ // Do nothing
721
+ }
722
+ }
723
+ return handlePotentialUniversalLink(url, context);
724
+ }
725
+ // Handle potential universal link
726
+ private boolean handlePotentialUniversalLink(String url, Context context) {
727
+ Log.e("Universal", "Enter: " + url);
728
+ Uri uri = Uri.parse(url);
729
+
730
+ // Let WebView handle other URLs
731
+ if (!isKnownAppLinkDomain(uri.getHost())) {
732
+ return false;
733
+ }
734
+
735
+ try {
736
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
737
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
738
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
739
+
740
+ try {
741
+ context.startActivity(intent); // make sure "context" exists in this class
742
+ return true;
743
+ } catch (ActivityNotFoundException e) {
744
+ // No app can handle it, let WebView handle
745
+ return false;
746
+ }
747
+ } catch (Exception e) {
748
+ Log.e("Universal", "Error with universal link: " + url, e);
749
+ return false;
750
+ }
751
+ }
752
+
753
+ private boolean isKnownAppLinkDomain(String host) {
754
+ if (host == null) return false;
755
+
756
+ String[] knownDomains = new String[] {
757
+ "tngdigital.com.my"
758
+ // Add other partner domains
759
+ };
760
+
761
+ for (String d : knownDomains) {
762
+ if (host.endsWith(d)) return true;
763
+ }
764
+ return false;
765
+ }
766
+
767
+ private String randomRequestId() {
768
+ return UUID.randomUUID().toString();
769
+ }
770
+
771
+ private String toBase64(String raw) {
772
+ String s = Base64.encodeToString(raw.getBytes(), Base64.NO_WRAP);
773
+ if (s.endsWith("=")) {
774
+ s = s.substring(0, s.length() - 2);
775
+ }
776
+ return s;
777
+ }
778
+
779
+ //
780
+ // void handleRedirect(String currentUrl, Response response) {
781
+ // String loc = response.header("Location");
782
+ // _webView.evaluateJavascript("");
783
+ // }
784
+ //
785
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
786
+ @Override
787
+ public WebResourceResponse shouldInterceptRequest(
788
+ WebView view,
789
+ WebResourceRequest request
790
+ ) {
791
+ Pattern pattern = _options.getProxyRequestsPattern();
792
+ if (pattern == null) {
793
+ return null;
794
+ }
795
+ Matcher matcher = pattern.matcher(request.getUrl().toString());
796
+ if (!matcher.find()) {
797
+ return null;
798
+ }
799
+
800
+ // Requests matches the regex
801
+ if (Objects.equals(request.getMethod(), "POST")) {
802
+ // Log.e("HTTP", String.format("returned null (ok) %s", request.getUrl().toString()));
803
+ return null;
804
+ }
805
+
806
+ Log.i(
807
+ "InAppBrowserProxy",
808
+ String.format("Proxying request: %s", request.getUrl().toString())
809
+ );
810
+
811
+ // We need to call a JS function
812
+ String requestId = randomRequestId();
813
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
814
+ proxiedRequestsHashmap.put(requestId, proxiedRequest);
815
+
816
+ // lsuakdchgbbaHandleProxiedRequest
817
+ activity.runOnUiThread(
818
+ new Runnable() {
819
+ @Override
820
+ public void run() {
821
+ StringBuilder headers = new StringBuilder();
822
+ Map<String, String> requestHeaders =
823
+ request.getRequestHeaders();
824
+ for (Map.Entry<
825
+ String,
826
+ String
827
+ > header : requestHeaders.entrySet()) {
828
+ headers.append(
829
+ String.format(
830
+ "h[atob('%s')]=atob('%s');",
831
+ toBase64(header.getKey()),
832
+ toBase64(header.getValue())
833
+ )
834
+ );
835
+ }
836
+ String s = String.format(
837
+ "try {function getHeaders() {const h = {}; %s return h}; window.InAppBrowserProxyRequest(new Request(atob('%s'), {headers: getHeaders(), method: '%s'})).then(async (res) => Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: true, result: (!!res ? {headers: Object.fromEntries(res.headers.entries()), code: res.status, body: (await res.text())} : null), id: '%s'})).catch((e) => Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: false, result: e.toString(), id: '%s'}))} catch (e) {Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: false, result: e.toString(), id: '%s'})}",
838
+ headers,
839
+ toBase64(request.getUrl().toString()),
840
+ request.getMethod(),
841
+ requestId,
842
+ requestId,
843
+ requestId
844
+ );
845
+ // Log.i("HTTP", s);
846
+ capacitorWebView.evaluateJavascript(s, null);
847
+ }
848
+ }
849
+ );
850
+
851
+ // 10 seconds wait max
852
+ try {
853
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
854
+ return proxiedRequest.response;
855
+ } else {
856
+ Log.e("InAppBrowserProxy", "Semaphore timed out");
857
+ proxiedRequestsHashmap.remove(requestId); // prevent mem leak
858
+ }
859
+ } catch (InterruptedException e) {
860
+ Log.e("InAppBrowserProxy", "Semaphore wait error", e);
861
+ }
862
+ return null;
863
+ }
864
+
865
+ @Override
866
+ public void onReceivedHttpAuthRequest(
867
+ WebView view,
868
+ HttpAuthHandler handler,
869
+ String host,
870
+ String realm
871
+ ) {
872
+ final String sourceUrl = _options.getUrl();
873
+ final String url = view.getUrl();
874
+ final JSObject credentials = _options.getCredentials();
875
+
876
+ if (
877
+ credentials != null &&
878
+ credentials.getString("username") != null &&
879
+ credentials.getString("password") != null &&
880
+ sourceUrl != null &&
881
+ url != null
882
+ ) {
883
+ String sourceProtocol = "";
884
+ String sourceHost = "";
885
+ int sourcePort = -1;
886
+ try {
887
+ URI uri = new URI(sourceUrl);
888
+ sourceProtocol = uri.getScheme();
889
+ sourceHost = uri.getHost();
890
+ sourcePort = uri.getPort();
891
+ if (
892
+ sourcePort == -1 && Objects.equals(sourceProtocol, "https")
893
+ ) sourcePort = 443;
894
+ else if (
895
+ sourcePort == -1 && Objects.equals(sourceProtocol, "http")
896
+ ) sourcePort = 80;
897
+ } catch (URISyntaxException e) {
898
+ e.printStackTrace();
899
+ }
900
+
901
+ String protocol = "";
902
+ int port = -1;
903
+ try {
904
+ URI uri = new URI(url);
905
+ protocol = uri.getScheme();
906
+ port = uri.getPort();
907
+ if (port == -1 && Objects.equals(protocol, "https")) port = 443;
908
+ else if (port == -1 && Objects.equals(protocol, "http")) port =
909
+ 80;
910
+ } catch (URISyntaxException e) {
911
+ e.printStackTrace();
912
+ }
913
+
914
+ if (
915
+ Objects.equals(sourceHost, host) &&
916
+ Objects.equals(sourceProtocol, protocol) &&
917
+ sourcePort == port
918
+ ) {
919
+ final String username = Objects.requireNonNull(
920
+ credentials.getString("username")
921
+ );
922
+ final String password = Objects.requireNonNull(
923
+ credentials.getString("password")
924
+ );
925
+ handler.proceed(username, password);
926
+ return;
927
+ }
928
+ }
929
+
930
+ super.onReceivedHttpAuthRequest(view, handler, host, realm);
931
+ }
932
+
933
+ @Override
934
+ public void onLoadResource(WebView view, String url) {
935
+ super.onLoadResource(view, url);
936
+ }
937
+
938
+ @Override
939
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
940
+ super.onPageStarted(view, url, favicon);
941
+ try {
942
+ URI uri = new URI(url);
943
+ if (TextUtils.isEmpty(_options.getTitle())) {
944
+ setTitle(uri.getHost());
945
+ }
946
+ } catch (URISyntaxException e) {
947
+ // Do nothing
948
+ }
949
+ }
950
+
951
+ public void doUpdateVisitedHistory(
952
+ WebView view,
953
+ String url,
954
+ boolean isReload
955
+ ) {
956
+ if (!isReload) {
957
+ _options.getCallbacks().urlChangeEvent(url);
958
+ }
959
+ super.doUpdateVisitedHistory(view, url, isReload);
960
+ injectJavaScriptInterface();
961
+ }
962
+
963
+ @Override
964
+ public void onPageFinished(WebView view, String url) {
965
+ super.onPageFinished(view, url);
966
+ if (!isInitialized) {
967
+ isInitialized = true;
968
+ _webView.clearHistory();
969
+ if (_options.isPresentAfterPageLoad()) {
970
+ boolean usePreShowScript =
971
+ _options.getPreShowScript() != null &&
972
+ !_options.getPreShowScript().isEmpty();
973
+ if (!usePreShowScript) {
974
+ show();
975
+ _options.getPluginCall().resolve();
976
+ } else {
977
+ executorService.execute(
978
+ new Runnable() {
979
+ @Override
980
+ public void run() {
981
+ if (
982
+ _options.getPreShowScript() != null &&
983
+ !_options.getPreShowScript().isEmpty()
984
+ ) {
985
+ injectPreShowScript();
986
+ }
987
+
988
+ activity.runOnUiThread(
989
+ new Runnable() {
990
+ @Override
991
+ public void run() {
992
+ show();
993
+ _options.getPluginCall().resolve();
994
+ }
995
+ }
996
+ );
997
+ }
998
+ }
999
+ );
1000
+ }
1001
+ }
1002
+ } else if (
1003
+ _options.getPreShowScript() != null &&
1004
+ !_options.getPreShowScript().isEmpty()
1005
+ ) {
1006
+ executorService.execute(
1007
+ new Runnable() {
1008
+ @Override
1009
+ public void run() {
1010
+ injectPreShowScript();
1011
+ }
1012
+ }
1013
+ );
1014
+ }
1015
+
1016
+ ImageButton backButton = _toolbar.findViewById(R.id.backButton);
1017
+ if (_webView.canGoBack()) {
1018
+ backButton.setImageResource(R.drawable.arrow_back_enabled);
1019
+ backButton.setEnabled(true);
1020
+ } else {
1021
+ backButton.setImageResource(R.drawable.arrow_back_disabled);
1022
+ backButton.setEnabled(false);
1023
+ }
1024
+
1025
+ ImageButton forwardButton = _toolbar.findViewById(R.id.forwardButton);
1026
+ if (_webView.canGoForward()) {
1027
+ forwardButton.setImageResource(R.drawable.arrow_forward_enabled);
1028
+ forwardButton.setEnabled(true);
1029
+ } else {
1030
+ forwardButton.setImageResource(R.drawable.arrow_forward_disabled);
1031
+ forwardButton.setEnabled(false);
1032
+ }
1033
+
1034
+ _options.getCallbacks().pageLoaded();
1035
+ injectJavaScriptInterface();
1036
+ }
1037
+
1038
+ @Override
1039
+ public void onReceivedError(
1040
+ WebView view,
1041
+ WebResourceRequest request,
1042
+ WebResourceError error
1043
+ ) {
1044
+ super.onReceivedError(view, request, error);
1045
+ _options.getCallbacks().pageLoadError();
1046
+ }
1047
+
1048
+ @SuppressLint("WebViewClientOnReceivedSslError")
1049
+ @Override
1050
+ public void onReceivedSslError(
1051
+ WebView view,
1052
+ SslErrorHandler handler,
1053
+ SslError error
1054
+ ) {
1055
+ boolean ignoreSSLUntrustedError = _options.ignoreUntrustedSSLError();
1056
+ if (
1057
+ ignoreSSLUntrustedError &&
1058
+ error.getPrimaryError() == SslError.SSL_UNTRUSTED
1059
+ ) handler.proceed();
1060
+ else {
1061
+ super.onReceivedSslError(view, handler, error);
1062
+ }
1063
+ }
1064
+ }
1065
+ );
1066
+ }
1067
+
1068
+ @Override
1069
+ public void onBackPressed() {
1070
+ if (
1071
+ _webView.canGoBack() &&
1072
+ (TextUtils.equals(_options.getToolbarType(), "navigation") ||
1073
+ _options.getActiveNativeNavigationForWebview())
1074
+ ) {
1075
+ _webView.goBack();
1076
+ } else if (!_options.getDisableGoBackOnNativeApplication()) {
1077
+ super.onBackPressed();
1078
+ }
1079
+ }
1080
+
1081
+ public static String getReasonPhrase(int statusCode) {
1082
+ switch (statusCode) {
1083
+ case (200):
1084
+ return "OK";
1085
+ case (201):
1086
+ return "Created";
1087
+ case (202):
1088
+ return "Accepted";
1089
+ case (203):
1090
+ return "Non Authoritative Information";
1091
+ case (204):
1092
+ return "No Content";
1093
+ case (205):
1094
+ return "Reset Content";
1095
+ case (206):
1096
+ return "Partial Content";
1097
+ case (207):
1098
+ return "Partial Update OK";
1099
+ case (300):
1100
+ return "Mutliple Choices";
1101
+ case (301):
1102
+ return "Moved Permanently";
1103
+ case (302):
1104
+ return "Moved Temporarily";
1105
+ case (303):
1106
+ return "See Other";
1107
+ case (304):
1108
+ return "Not Modified";
1109
+ case (305):
1110
+ return "Use Proxy";
1111
+ case (307):
1112
+ return "Temporary Redirect";
1113
+ case (400):
1114
+ return "Bad Request";
1115
+ case (401):
1116
+ return "Unauthorized";
1117
+ case (402):
1118
+ return "Payment Required";
1119
+ case (403):
1120
+ return "Forbidden";
1121
+ case (404):
1122
+ return "Not Found";
1123
+ case (405):
1124
+ return "Method Not Allowed";
1125
+ case (406):
1126
+ return "Not Acceptable";
1127
+ case (407):
1128
+ return "Proxy Authentication Required";
1129
+ case (408):
1130
+ return "Request Timeout";
1131
+ case (409):
1132
+ return "Conflict";
1133
+ case (410):
1134
+ return "Gone";
1135
+ case (411):
1136
+ return "Length Required";
1137
+ case (412):
1138
+ return "Precondition Failed";
1139
+ case (413):
1140
+ return "Request Entity Too Large";
1141
+ case (414):
1142
+ return "Request-URI Too Long";
1143
+ case (415):
1144
+ return "Unsupported Media Type";
1145
+ case (416):
1146
+ return "Requested Range Not Satisfiable";
1147
+ case (417):
1148
+ return "Expectation Failed";
1149
+ case (418):
1150
+ return "Reauthentication Required";
1151
+ case (419):
1152
+ return "Proxy Reauthentication Required";
1153
+ case (422):
1154
+ return "Unprocessable Entity";
1155
+ case (423):
1156
+ return "Locked";
1157
+ case (424):
1158
+ return "Failed Dependency";
1159
+ case (500):
1160
+ return "Server Error";
1161
+ case (501):
1162
+ return "Not Implemented";
1163
+ case (502):
1164
+ return "Bad Gateway";
1165
+ case (503):
1166
+ return "Service Unavailable";
1167
+ case (504):
1168
+ return "Gateway Timeout";
1169
+ case (505):
1170
+ return "HTTP Version Not Supported";
1171
+ case (507):
1172
+ return "Insufficient Storage";
1173
+ default:
1174
+ return "";
1175
+ }
1176
+ }
1177
+ }