Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ ext {
}
```

#### LocalStorage Isolation
The `openInWebView` option provides isolation for `localStorage` and `cookies` to ensure that content loaded in the InAppBrowser does not interfere with the main application's storage.

- **iOS**: Storage is isolated by default.
- **Android (API 28+)**: Storage is **isolated by default** by running the InAppBrowser in a separate process (`:OSInAppBrowser`) with a dedicated data directory suffix.
- **Android (API < 28)**: Storage is **shared** with the main application due to platform limitations.

### Opting-out of Isolation (Android)
If your use case requires sharing `localStorage` or `cookies` between the main app and the InAppBrowser on Android, you can opt-out of isolation by setting `isIsolated: false` in the `android` options.

:::caution

Disabling isolation reduces the security of your app by allowing potentially untrusted web content to access your application's private storage (Cookies, LocalStorage, etc.). Use this only if absolutely necessary.

:::

:::warning

**Breaking Change (Android)**: Apps upgrading to this version will lose any existing `localStorage` or cookies previously stored by the InAppBrowser on the first run. This is because the WebView now runs in a separate process with its own data directory. Users may need to re-authenticate with websites that relied on persisted session data.

:::

---

## Usage Example
#### Open In External Browser
```typescript
Expand Down Expand Up @@ -239,6 +263,7 @@ Defines the options for opening a URL in the web view.
| **`allowZoom`** | <code>boolean</code> | Shows the Android browser's zoom controls. |
| **`hardwareBack`** | <code>boolean</code> | Uses the hardware back button to navigate backwards through the Web View's history. If there is no previous page, the Web View will close. |
| **`pauseMedia`** | <code>boolean</code> | Makes the Web View pause/resume with the app to stop background audio. |
| **`isIsolated`** | <code>boolean</code> | Whether to run the InAppBrowser in an isolated process. Android only. Defaults to true. |


#### iOSWebViewOptions
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ repositories {
dependencies {
// implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation("io.ionic.libs:ioninappbrowser-android:1.6.2")
implementation("io.ionic.libs:ioninappbrowser-android:2.0.0")
implementation "androidx.browser:browser:$androidxBrowserVersion"
implementation "androidx.constraintlayout:constraintlayout:2.2.1"
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ class InAppBrowserPlugin : Plugin() {
allowZoom = androidOptions?.getBoolean("allowZoom", true) ?: true,
hardwareBack = androidOptions?.getBoolean("hardwareBack", true) ?: true,
pauseMedia = androidOptions?.getBoolean("pauseMedia", true) ?: true,
customUserAgent = it.getString("customWebViewUserAgent", null)
customUserAgent = it.getString("customWebViewUserAgent", null),
isIsolated = androidOptions?.getBoolean("isIsolated", true) ?: true
)
}
}
Expand Down
5 changes: 5 additions & 0 deletions example-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Redirect, Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import Home from './pages/Home';
import LocalStorageTest from './pages/LocalStorageTest';


/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';
Expand Down Expand Up @@ -42,6 +44,9 @@ const App: React.FC = () => (
<Route exact path="/home">
<Home />
</Route>
<Route exact path="/local-storage-test">
<LocalStorageTest />
</Route>
<Route exact path="/">
<Redirect to="/home" />
</Route>
Expand Down
77 changes: 74 additions & 3 deletions example-app/src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { IonButton, IonContent, IonHeader, IonInput, IonPage, IonTitle, IonToolbar } from '@ionic/react';
import React, { useState } from 'react';
import { IonButton, IonContent, IonHeader, IonInput, IonPage, IonTitle, IonToolbar, useIonRouter, IonItem, IonLabel, IonToggle } from '@ionic/react';
import { InAppBrowser, DefaultSystemBrowserOptions, DefaultWebViewOptions, DefaultAndroidWebViewOptions, DismissStyle, iOSViewStyle, iOSAnimation, ToolbarPosition, AndroidViewStyle, AndroidAnimation, BrowserPageNavigationCompletedEventData } from '@capacitor/inappbrowser';
import './Home.css';

const Home: React.FC = () => {

const [testUrl, setTestUrl] = useState("https://mdn.github.io/dom-examples/web-storage/");
const [showIframe, setShowIframe] = useState(false);
const [isIsolated, setIsIsolated] = useState(true);

const openInExternalBrowser = () => {
InAppBrowser.openInExternalBrowser({
url: "https://www.google.com"
Expand All @@ -22,7 +27,7 @@ const Home: React.FC = () => {
alert("Error: Unknown error");
}
}

}

const openInSystemBrowserWithDefaults = () => {
Expand Down Expand Up @@ -151,6 +156,32 @@ const Home: React.FC = () => {
InAppBrowser.close();
}

const router = useIonRouter();

const toggleIframe = () => {
setShowIframe(!showIframe);
};

const openLocalStorageTestInWebView = () => {
// We open the local storage test page in the InAppBrowser
// NOTE: On Android with Process Isolation, 'http://localhost' may 404
// because the new process doesn't have the Capacitor local server.
// For a true same-origin test, use a remote URL.
InAppBrowser.openInWebView({
url: testUrl,
options: {
...DefaultWebViewOptions,
showURL: true,
showToolbar: true,
clearCache: false,
android: {
...DefaultAndroidWebViewOptions,
isIsolated: isIsolated
}
}
});
}

InAppBrowser.addListener('browserClosed', () => {
console.log("browser was closed.");
});
Expand Down Expand Up @@ -185,8 +216,48 @@ const Home: React.FC = () => {
<IonButton onClick={openInWebViewWithMoreCustomValues}>Web View with More Custom Values</IonButton>
<IonButton onClick={openInSystemBrowserThenClose}>Open System Browser then Close</IonButton>
<IonButton onClick={openInWebViewThenClose}>Open Web View then Close</IonButton>
<IonButton onClick={close}>Close opened Browser</IonButton>
<IonButton onClick={invalidScheme}>Invalid URL Scheme</IonButton>

<div style={{ padding: '10px', margin: '10px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h3>Isolation Test</h3>
<IonItem>
<IonLabel position="stacked">Test URL</IonLabel>
<IonInput value={testUrl} onIonChange={e => setTestUrl(e.detail.value!)} />
</IonItem>
<IonItem>
<IonLabel>Enable Storage Isolation (Android)</IonLabel>
<IonToggle checked={isIsolated} onIonChange={e => setIsIsolated(e.detail.checked)} />
</IonItem>
<p style={{ fontSize: '0.8em', color: '#666' }}>
<strong>To replicate shared storage on API &lt; 28:</strong><br />
1. Show the "Site Setter" below.<br />
2. Use the embedded page to set a value.<br />
3. Tap "2. Open Site (InAppBrowser)". It should share the value.<br />
<br />
<strong>On API 28+ (Isolated):</strong><br />
The value will NOT be shared.
</p>
<IonButton expand="block" color="secondary" onClick={toggleIframe}>
{showIframe ? "Hide Site Setter" : "1. Show Site Setter (Main App)"}
</IonButton>

{showIframe && (
<div style={{ margin: '10px 0', border: '2px solid #3880ff', borderRadius: '4px', overflow: 'hidden' }}>
<p style={{ padding: '5px', margin: 0, backgroundColor: '#3880ff', color: 'white', fontSize: '0.8em', textAlign: 'center' }}>
Main App Context (Iframe)
</p>
<iframe
src={testUrl}
style={{ width: '100%', height: '300px', border: 'none' }}
title="Storage Setter"
/>
</div>
)}

<IonButton expand="block" color="tertiary" onClick={openLocalStorageTestInWebView}>2. Open Site (InAppBrowser)</IonButton>
<IonButton expand="block" fill="outline" onClick={() => router.push('/local-storage-test')}>Go to Local Test Component</IonButton>
</div>

<IonInput placeholder="Enter text here..." />
</div>
</IonContent>
Expand Down
73 changes: 73 additions & 0 deletions example-app/src/pages/LocalStorageTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useState, useEffect } from 'react';
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton, IonInput, IonItem, IonLabel, IonList, IonButtons, IonBackButton } from '@ionic/react';

const LocalStorageTest: React.FC = () => {
const [value, setValue] = useState('');
const [storedValue, setStoredValue] = useState<string | null>(null);

const refreshValue = () => {
const val = localStorage.getItem('inappbrowser_test_key');
setStoredValue(val);
};

useEffect(() => {
refreshValue();
}, []);

const saveValue = () => {
localStorage.setItem('inappbrowser_test_key', value);
refreshValue();
};

const clearValue = () => {
localStorage.removeItem('inappbrowser_test_key');
refreshValue();
};

return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/home" />
</IonButtons>
<IonTitle>LocalStorage Test</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<IonList>
<IonItem>
<IonLabel position="stacked">Test Value</IonLabel>
<IonInput value={value} onIonChange={(e: any) => setValue(e.detail.value!)} placeholder="Enter value to store" />
</IonItem>
</IonList>
<div style={{ marginTop: '20px' }}>
<IonButton expand="block" onClick={saveValue}>Save to LocalStorage</IonButton>
<IonButton expand="block" color="light" onClick={refreshValue}>Refresh from LocalStorage</IonButton>
<IonButton expand="block" color="danger" onClick={clearValue}>Clear LocalStorage</IonButton>
</div>

<div style={{ marginTop: '40px' }}>
<h3>Stored Value:</h3>
<p style={{ fontSize: '24px', fontWeight: 'bold' }}>{storedValue || '(none)'}</p>
</div>

<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f0f0f0', borderRadius: '8px' }}>
<p><strong>Instructions:</strong></p>
<ol>
<li>Set a value here in the main app.</li>
<li>Go back and open this same page using "Open Site (InAppBrowser)".</li>
<li>Use the <strong>Enable Storage Isolation</strong> toggle to test different modes.
<ul>
<li><strong>On (Default)</strong>: Value should be "(none)".</li>
<li><strong>Off</strong>: Value should be shared.</li>
</ul>
</li>
</ol>
</div>
</IonContent>
</IonPage>
);
};

export default LocalStorageTest;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"build": "npm run clean && npm run docgen && vite build",
"clean": "rimraf ./dist",
"watch": "tsc --watch",
"prepare": "npm run build",
"prepublishOnly": "npm run build",
"semantic-release": "semantic-release"
},
Expand Down Expand Up @@ -96,4 +97,4 @@
"src": "android"
}
}
}
}
1 change: 1 addition & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const DefaultAndroidWebViewOptions: AndroidWebViewOptions = {
allowZoom: false,
hardwareBack: true,
pauseMedia: true,
isIsolated: true,
};

export const DefaultiOSWebViewOptions: iOSWebViewOptions = {
Expand Down
2 changes: 2 additions & 0 deletions src/definitions.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about marking this as a breaking change / major version in the Capacitor plugin: Either the squash-commit needs to have a BREAKING CHANGE: <describe breaking change> in the commit footer so that semantic-release does the major version bump (should try a dry-run locally on main just to check), or we do a manual release.

Plus (not applicable to this PR specifically) we should create a 3.x branch before this PR is merged, but as for releasing patches in 3.x, I'm not sure how to approach this (2.x is going to latest-7, would 3.x go to latest-8, and where would 4.x go to if we do Capacitor-9 related breaking changes in a 5.x?), because up until now we've had one major version per Capacitor major version, think this will be the first plugin to break this rule. Maybe this needs to be brought up to the team internally, but mentioning it here because I think we haven't had this dicussion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the exclamation mark just in case, but will also add it to the footer of the squashed commit.

As far as the versioning, you bring up a valid point. I think in this case, we should just create a 3.x branch and merge this there along with any other Capacitor 9 upgrades we will do in the upcoming weeks. This "bug" wasn't reported by any customer / user except internally so I don't believe it is very urgent.

For future breaking changes, it will probably be a case-by-case basis, but if we target only 1 release of a major version per year for Capacitor.. then we will probably eventually run into scenarios where we will have to break the rule, especially if its an urgent bug fix or something

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the exclamation mark just in case

Exclamation mark won't work, because semantic-release in this repo is following Angular Commit convention, not conventional commits, where there's no "!"; I'd say probably it's best not to have one.

As far as the versioning, you bring up a valid point. I think in this case, we should just create a 3.x branch and merge this there along with any other Capacitor 9 upgrades we will do in the upcoming weeks. This "bug" wasn't reported by any customer / user except internally so I don't believe it is very urgent.

Not sure I follow. If we want to bring this fix to OutSystems, we need to update the Capacitor plugin and use it on ODC.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I meant to advocate for making the 3.x branch, but only merge this change to main. That way it is only included with the next major release which will likely be with the other Capacitor 9 upgrades. I don't think this needs to make it into the current version since it's not as urgent and might be weird for us to be making 2 major releases for a single Capacitor version unless it's necessary, especially since there isn't a precedent and then we'd be releasing another major release right after.

I will bring it up to the team to see their thoughts because I think it is an important discussion

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per internal discussions we're fine with raising the major version now, just keep in mind the initial comment I made that I think is still relevant regarding how to do the version bump + (and I don't think we discussed this with the team, or maybe we did and I forgot) should we keep maintaining 3.x after releasing 4.x?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I will add the BREAKING CHANGE: in the commit footer.

As far as maintenance goes for 3.x, I don't think it's absolutely necessary if we don't have the bandwidth as users can upgrade to 4.x and keep the same behavior by simply disabling isIsolated. However, it also wouldn't hurt to just cherry pick all of the changes to 3.x to be safe. In the past we have always just supported the current version and one previous major version, so if we follow that pattern then maybe we maintain it until 5.x comes out.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one doubt I have is what npm tag would 3.x go to? because we have been using latest-Y where Y is the capacitor framework major version that the plugin version is compatible with. Since there'd be two (3.x and 4.x), unsure where that would go

But this isn't something that we need right now I guess. As long as we have 3.x branch, we can set the rest up any other time if we find it's better to have those two versions, along with 2.x as well (for Cap 7 until later this year).

Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ export interface AndroidWebViewOptions {
hardwareBack: boolean;
/** Makes the Web View pause/resume with the app to stop background audio. */
pauseMedia: boolean;
/** Whether to run the InAppBrowser in an isolated process. Android only. Defaults to true. */
isIsolated?: boolean;
}

/* eslint-disable no-unused-vars */
Expand Down