Skip to content
Merged
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
45 changes: 30 additions & 15 deletions src/control/content/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
EXTERNAL_BROWSER: 'External browser'
};

$scope.viewSubtype = {
IFRAME: 'iFrame',
NATIVE_WEBVIEW: 'Native webview'
};

buildfire.datastore.get(function(err, result) {
if (err) return console.error('Error: ', err);

Expand All @@ -32,11 +37,17 @@
$scope.data.content.view = $scope.viewType.IN_APP_POPUP;
}
}

// Backward compatibility: If viewType.NATIVE_IN_APP was selected before, default to viewSubtype.IFRAME
if ($scope.data.content.view === $scope.viewType.NATIVE_IN_APP && !$scope.data.content.viewSubtype) {
$scope.data.content.viewSubtype = $scope.viewSubtype.IFRAME;
}
} else {
$scope.data = {
content: {
url: '',
view: $scope.viewType.IN_APP_POPUP
view: $scope.viewType.IN_APP_POPUP,
viewSubtype: null // Initialize viewSubtype
}
};
}
Expand Down Expand Up @@ -118,22 +129,26 @@
};

$scope.changeViewType = function() {
dataChanged = true;
if ($scope.frmMain.$invalid) return;
dataChanged = true;
if ($scope.frmMain.$invalid) return;

var data = $scope.data;
if ($scope.data.content.view === $scope.viewType.NATIVE_IN_APP && !$scope.data.content.viewSubtype) {
$scope.data.content.viewSubtype = $scope.viewSubtype.IFRAME;
}

if (data.content.openInApp != undefined) {
data.content.openInApp = null;
}
buildfire.datastore.save(data, function(err, result) {
if (err || !result) {
$log.error('Error saving the widget details: ', err);
} else {
$log.info('Widget details saved');
}
});
var data = $scope.data;

if (data.content.openInApp != undefined) {
data.content.openInApp = null;
}
buildfire.datastore.save(data, function(err, result) {
if (err || !result) {
$log.error('Error saving the widget details: ', err);
} else {
$log.info('Widget details saved');
}
});
};

$scope.openMethodChanged = function() {
Expand Down
73 changes: 51 additions & 22 deletions src/control/content/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@
display: block !important;
padding-bottom: 5px;
}
p.info-note {
.note {
padding: 12px 20px;
margin-top: 15px;
margin-bottom: 15px;
background: #F5F5F5;
}
p.info-note.text-warning {
color: var(--c-warning);
background-color: var(--c-gray1);
.note .bold {
font-weight: 600;
}
.image-container img {
margin: 10px;
border-radius: 8px;
box-shadow: 0 0 8px 2px rgba(0,0,0,0.10);
}
</style>
</head>
Expand Down Expand Up @@ -73,6 +80,20 @@
</div>
<hr class="none">
<div class="item clearfix row">
<div class="image-container col-md-3 pull-left">
<img
ng-if="data.content.view === viewType.NATIVE_IN_APP"
ng-src="../../resources/native-webview.jpg"
alt="Native Webview Preview">
<img
ng-if="data.content.view === viewType.IN_APP_POPUP"
ng-src="../../resources/popup-window.jpg"
alt="Popup Window Preview">
<img
ng-if="data.content.view === viewType.EXTERNAL_BROWSER"
ng-src="../../resources/external-browser.jpg"
alt="External Browser Preview">
</div>
<div class="col-md-9 pull-right">
<div class="radio radio-primary radio-block">
<input id="popup" type="radio" ng-model="data.content.view"
Expand All @@ -86,30 +107,38 @@
name="viewType" ng-change="changeViewType()">
<label for="inapp">In Feature</label>
</div>
<p ng-if="data.content.view === viewType.NATIVE_IN_APP" class="info-note text-warning" style="width: 100%;">
Some websites <strong>may not function properly</strong> in the in-feature WebView due to <strong>Apple's restrictions on cross-origin iframes</strong>, which can affect login and state management, or due to internal policies of certain sites like Google and Amazon that prevent them from being displayed in the app’s native component. If your web page does not appear or work as expected, we recommend using one of the alternative methods available.
</p>
<div ng-if="data.content.view === viewType.NATIVE_IN_APP" class="flex margin-left-twenty">
<div class="radio radio-primary radio-block">
<input id="insideIframe" type="radio" ng-model="data.content.viewSubtype"
ng-value="viewSubtype.IFRAME"
name="viewSubtype" ng-change="changeViewType()">
<label for="insideIframe">iFrame</label>
</div>
<div class="radio radio-primary radio-block margin-top-ten margin-left-twenty">
<input id="nativeWebview" type="radio" ng-model="data.content.viewSubtype"
ng-value="viewSubtype.NATIVE_WEBVIEW"
name="viewSubtype" ng-change="changeViewType()">
<label for="nativeWebview">Native Webview (Beta)</label>
</div>
</div>
<div class="note" ng-if="data.content.view === viewType.NATIVE_IN_APP && data.content.viewSubtype === viewSubtype.IFRAME">
<span class="bold">Some websites may not work correctly when opened inside the app, especially in the built-in browser view.</span> This is often due to Apple’s security rules or website settings from companies like Google and Amazon that block their pages from being shown in this way. If your webpage doesn’t load or work as expected, we recommend trying one of the other viewing options available.
</div>
<div class="note" ng-if="data.content.view === viewType.NATIVE_IN_APP && data.content.viewSubtype === viewSubtype.NATIVE_WEBVIEW">
Native Webview is a new feature that works around iFrame issues. If the iFrame option isn’t working, try using Native Webview instead.
<div class="bold margin-top-twenty">Note: This feature will only work for apps with builds submitted to Apple App Store and Google Play Store after the 5, August 2025. For older builds the app will default to "In Popup Window" option.</div>
</div>
<div class="radio radio-primary radio-block">
<input id="external" type="radio" ng-model="data.content.view"
ng-value="viewType.EXTERNAL_BROWSER"
name="viewType" ng-change="changeViewType()">
<label for="external">Device's default external browser</label>
</div>
<br>

<p class="info padding-zero">Note 1: For the best user experience, link to
mobile optimized content. <br>(Disclaimer: Some websites will show
mobile version of site only on mobile devices.)</p>

<p class="info padding-zero">Note 2: If you are taking payments through
this web page, you must open the the URL outside of the app or the app
will get rejected by Apple in the submission review process.</p>

<p class="info padding-zero">Note 3: It is best to use secure websites (<strong>Https</strong>) some operating systems require it.</p>

<p class="info padding-zero">Hint: If you are taking payments, use your
app or content DeepLink URL to redirect back to the app after a payment
is made.</p>
<div class="note">
<div>For the best experience, link to mobile-optimized and secure (HTTPS) content. If your webpage includes payment processing, it must open outside of the app—otherwise, Apple may reject your app during review. Also, be aware that some websites only display their mobile layout on actual mobile devices.</div>
<div class="bold margin-top-twenty">Note: If you’re accepting payments, use a Deep Link to redirect users back into your app after the transaction is complete.</div>
</div>

</div>
</div>
</div>
Expand Down
Binary file added src/resources/external-browser.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/native-webview.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/popup-window.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/widget/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
.modal-header, .cancel-confirmation {
display: none;
}
#webviewReload {
display: none;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
</style>
</head>
<body class="no-scroll">
Expand All @@ -42,5 +49,9 @@ <h2>Success!</h2>
<div id="warning-message_wrapper">
<div id="warning-message"></div>
</div>

<div id="webviewReload">
<h4>Click <a onclick="location.reload();" class="text-primary">here</a> to reload</h4>
</div>
</body>
</html>
45 changes: 28 additions & 17 deletions src/widget/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ const { formatSSO, formatOAuth } = require('./formatSSO');
const viewOptions = {
POPUP: 'In app popup',
NATIVE: 'Native In App',
EXTERNAL: 'External browser'
EXTERNAL: 'External browser',
NATIVE_WEBVIEW: 'Native webview',
};

const flags = {};

const setFlags = (content) => {
flags.isWeb = (buildfire.context.device.platform == 'web');
flags.shouldOpenInApp = (content.view == viewOptions.NATIVE);
flags.shouldOpenPluginInboundWebview = (content.view == viewOptions.NATIVE && content.viewSubtype == viewOptions.NATIVE_WEBVIEW);
flags.isLiveMode = buildfire.context.liveMode;
flags.isNotCP = (flags.isLiveMode === 1 || !flags.isWeb);
flags.requiresSSO = content.url && content.url.indexOf('{{SSO}}') > 0;
Expand All @@ -20,37 +22,45 @@ const setFlags = (content) => {

const render = (content) => {

const handleWindow = (openWindow, displayIniFrame, displaySuccessMessage) => {
const handleWindow = ({openWindow, displayIniFrame, shouldOpenPluginInboundWebview, displaySuccessMessage}) => {
if(openWindow){
setTimeout(() => buildfire.navigation.goBack(), 750);

if(content.view === viewOptions.POPUP){
if(flags.isWeb){
renderiFrame({url: content.url, isIOS: flags.isIOS});
return;
} else {
buildfire.navigation.openWindow(content.url, "_blank");
}
}
else
buildfire.navigation.openWindow(content.url, "_system");

return;
}
if(displayIniFrame){
} else if (shouldOpenPluginInboundWebview) {
if(flags.isWeb){
renderiFrame({url: content.url, isIOS: flags.isIOS});
} else {
// Show the title bar and open the window
buildfire.appearance.titlebar.show(null, (err) => {
if (err) return console.error(err);
buildfire.navigation.openWindow({url: content.url, target: "_plugin", windowFeatures: "pushToHistory=true"});
buildfire.messaging.onReceivedBroadcast = (event) => {
if (event.message === 'webview hidden') {
document.getElementById('webviewReload').style.display = 'none';
} else if (event.message === 'webview closed') {
document.getElementById('webviewReload').style.display = 'flex';
}
};
});
}
} else if(displayIniFrame){
renderiFrame({url: content.url, isIOS: flags.isIOS});
return;
}
if(displaySuccessMessage){
} else if(displaySuccessMessage){
if(flags.isWeb){
renderiFrame({url: content.url, isIOS: flags.isIOS});
return;
} else {
window.document.getElementById('successMessage').style.display = 'block';
window.document.getElementById('targetUrl').href = content.url;
return;
}

}
};

Expand All @@ -59,26 +69,27 @@ const render = (content) => {
setFlags(content);
const displayIniFrame = flags.shouldOpenInApp; //on the device and open native
const openWindow = flags.isNotCP && !flags.shouldOpenInApp; //on the device and open in pop up or native brow
const shouldOpenPluginInboundWebview = flags.isNotCP && flags.shouldOpenPluginInboundWebview; //on the device and open in webview
const displaySuccessMessage = content.url && flags.isWeb && !flags.isLiveMode;

if (flags.requiresSSO) { //This is an SSO webview with an access token
buildfire.auth.getCurrentUser((err, result) => {
if (result && result.SSO && result.SSO.accessToken) {
content.url = formatSSO(content.url, JSON.stringify(result.SSO));
handleWindow(openWindow, displayIniFrame, displaySuccessMessage);
handleWindow({openWindow, displayIniFrame, shouldOpenPluginInboundWebview, displaySuccessMessage});
}
else{
if (result && result.oauthProfile && result.oauthProfile.accessToken) {
content.url = formatOAuth(content.url, result.oauthProfile.accessToken);
handleWindow(openWindow, displayIniFrame, displaySuccessMessage);
handleWindow({openWindow, displayIniFrame, shouldOpenPluginInboundWebview, displaySuccessMessage});
}
else{
handleWindow(openWindow, displayIniFrame, displaySuccessMessage);
handleWindow({openWindow, displayIniFrame, shouldOpenPluginInboundWebview, displaySuccessMessage});
}
}
});
} else { //this is all other URLs, i.e. no SSO.
handleWindow(openWindow, displayIniFrame, displaySuccessMessage);
handleWindow({openWindow, displayIniFrame, shouldOpenPluginInboundWebview, displaySuccessMessage});
}

};
Expand Down