This open-source library allows you to integrate Mini App ecosystem into your iOS applications. Mini App SDK also facilitates communication between a Mini App and the host app via a message bridge.
- Load MiniApp list
- Load MiniApp metadata
- Create a MiniAppView
- Facilitate communication between host app and Mini App
And much more features which you can find them in Usage.
All the MiniApp files downloaded by the MiniApp iOS library are cached locally
This module supports iOS 14.0 and above. It has been tested on iOS 14.0 and above.
It is written in Swift 5.0 and can be used in compatible Xcode versions.
Note: This module is currently set to deployment_target = '14.0'
Mini App SDK is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'MiniApp'
The SDK can provide you some useful UI elements to help you displaying Mini Apps in your host app thanks to this subspec:
pod 'MiniApp/UI'
If you need to support display of Google ads (up to version 8) triggered from your Mini App, you need to add the following subspec instead:
pod 'MiniApp/Admob'
or if you need version 8 or above
pod 'MiniApp/Admob8'
If you want to check the Mini App zip file integrity to prevent file corruption during download, adding MiniApp/Signature
subspec will automatically enable it.
By default if the verification fails, it will trigger an analytic event but won't prevent the Mini App to load. There is a runtime configuration to avoid a corrupted Mini App to be opened.
You can also provide a new default behavior by using a RMARequireMiniAppSignatureVerification
boolean parameter in the application plist file (see the configuration matrix below to know more about the RMARequireMiniAppSignatureVerification
parameter)
pod 'MiniApp/Signature'
Due to some dependencies limitations, the Carthage version of MiniApp SDK embbed all the features described above. It also needs the host projet to implement the latest Google Mobile Ads framework independently (see documentation). To depend on MiniApp SDK through Carthage add this line to you Cartfile:
github "https://github.com/rakutentech/ios-miniapp" "prod"
To integrate MiniApp SDK into your Xcode project using Swift Package Manager, add it to the dependencies value of your Package.swift
:
dependencies: [
.package(url: "https://github.com/rakutentech/ios-miniapp.git", .upToNextMajor(from: "5.6.0"))
]
In your project configuration .plist you should add below Key/Value :
Key | Type | Description | Optional | Default |
---|---|---|---|---|
RASProjectId | String | Set your MiniApp host application project identifier |
NO | none |
RASProjectSubscriptionKey | String | Set your MiniApp subscription key |
NO | none |
RMAAPIEndpoint | String | Provide your own Base URL for API requests |
NO | none |
RMASSLKeyHash | Dictionary | This is the certificate keys hashes used for SSL pinning. The dictionary contains 2 keys: [main] with the main pin, and [backup] for the backup pin . |
NO | none |
RMAHostAppUserAgentInfo | String | Host app name and version info that is appended in User agent. The value specified in the plist is retrieved only at the build time. |
YES | none |
RMARequireMiniAppSignatureVerification | Bool | This setting allows you to make the Mini App zip file signature validation mandatory. It is set to false by default, which means if a signature is not valid the mini app will still be launched |
YES | false |
Additionally, if you support Google ads with MiniApp/Admob subspec, you need to configure Google ads framework as advised into this documentation |
If you don't want to use project settings, you have to pass this information one by one to the Config.userDefaults
using a Config.Key
as key:
Config.userDefaults?.set("MY_CUSTOM_ID", forKey: Config.Key.subscriptionKey.rawValue)
- Configure MiniApp
- Create a MiniApp
- Loading a Miniapp from Bundle
- Download a Miniapp in Background
- Mini App Features
- Load the Mini App list
- Get a MiniAppInfo
- Mini App meta-data
- List Downloaded Mini apps
- Advanced Features
- Overriding configuration on runtime
- Overriding localizations
- Customize history navigation
- Opening external links
- Orientation Lock
- Catching analytics events
- Passing Query parameters while creating Mini App
- Permissions required from the Host app
- MiniApp events
- Load Mini app from Cache
- Keyboard Events
- MiniApp Cache available
- Import the MiniApp SDK in your
UIApplicationDelegate
:
import MiniApp
MiniApp.configure()
should be always called at launch byAppDelegate
.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
MiniApp.configure()
// ...
return true
}
API Docs:
NOTE: The method `create(appInfo: ,queryParams: ,completionHandler: ,messageInterface: ,adsDisplayer: ,fromCache: ) in `MiniApp` class will be deprecated in the upcoming release of MiniAppSDK, `load(fromCache: , completion: )` from `MiniAppView` must be used as shown below.
MiniAppView
is used to create a View
for displaying a specific Mini App. You must provide the Mini App ID which you wish to create (you can get the Mini App ID by Loading the Mini App List first). Calling MiniAppView
's load
method will do the following:
- Checks with the platform what is the latest and published version of the Mini App.
- Check if the latest version of the Mini App has been already downloaded
- If yes, return the already downloaded Mini App view.
- If not, download the latest version and then display the view
- If the device is disconnected from the internet and if the device already has a version of the Mini App downloaded, then the already downloaded version will be returned immediately.
The following is a simplified example:
let params = MiniAppParameters.default(
config: MiniAppConfig(config: Config.current(), messageInterface: self),
appId: "your-miniapp-id"
)
let miniAppView = MiniAppView(params: params)
self.view.addSubview(miniAppView)
miniAppView.frame = self.view.bounds
// load the miniapp
miniAppView.load { success in
if success {
// miniAppView is loaded
} else {
print("error: miniapp failed to load")
}
}
Miniapps can be loaded from Bundle now,
The following is a simplified example:
- Provide the filname of the bundle, followed by MiniApp ID and VersionID
MiniApp.unzipMiniApp(fileName: "js-miniapp-sample", miniAppId: "mini-app-testing-appid", versionId: "mini-app-testing-versionid")
- Your Miniapp will be extracted to the default location where all Miniapps are downloaded
- Load Miniapp from Bundle
// notice the new parameter for ads delegation
let params = MiniAppParameters.default(
config: MiniAppConfig(config: Config.current(), adsDisplayer: self, messageInterface: self),
appId: "mini-app-testing-appid",
version: "mini-app-testing-versionid"
)
let miniAppView = MiniAppView(params: params)
view.loadFromBundle {
// Returns a MiniAppWebView to load
}
Miniapps can be downloaded in background using the following method,
// Please pass the valid Miniapp ID and version ID that is uploaded and available in platform to start downloading.
// Once the downloading is completed, completionHandler will let you know if the downloading is success or a failure
MiniApp.shared(with: Config.current()).downloadMiniApp(appId: String, versionId: String, completionHandler: @escaping (Result<Bool, MASDKError>) -> Void) {
}
API Docs: MiniAppMessageDelegate
The MiniAppMessageDelegate
is used for passing messages between the Mini App (JavaScript) and the Host App (your native iOS App) and vice versa. Your App must provide the implementation for these functions.
Mini App SDK provides default implementation for few interfaces in MiniAppMessageDelegate
, however the Host app can still override them by implementing the interface in their side.
Method | Default |
---|---|
getUniqueId | 🚫 |
getMessagingUniqueId | 🚫 |
getMauid | 🚫 |
requestDevicePermission | 🚫 |
requestCustomPermissions | ✅ |
shareContent | ✅ |
getUserName | 🚫 |
getProfilePhoto | 🚫 |
getAccessToken | 🚫 |
sendJsonToHostApp | 🚫 |
getHostAppThemeColors | 🚫 |
didReceiveMAAnalytics | 🚫 |
NOTE: Following code snippets is an example for implementing MiniAppMessageDelegate methods, you can add your own custom implementation or you can make use of the code which is provided in the Sample app.
API Docs: MiniAppUserInfoDelegate
extension ViewController: MiniAppMessageDelegate {
func getUniqueId(completionHandler: @escaping (Result<String, MASDKError>) -> Void) {
// Implementation to return the Unique ID
completionHandler(.success(""))
}
}
NOTE: This method `getUniqueId(completionHandler:)` will be deprecated in the upcoming release of MiniAppSDK. You should use `getMessagingUniqueId(completionHandler:)` instead.
API Docs: MiniAppUserInfoDelegate
extension ViewController: MiniAppMessageDelegate {
func getMessagingUniqueId(completionHandler: @escaping (Result<String, MASDKError>) -> Void) {
// Implementation to return the Messaging Unique ID
completionHandler(.success(""))
}
}
API Docs: MiniAppUserInfoDelegate
extension ViewController: MiniAppMessageDelegate {
func getMauid(completionHandler: @escaping (Result<String, MASDKError>) -> Void) {
// Implementation to return the Unique MAUID
completionHandler(.success(""))
}
}
API Docs: MiniAppMessageDelegate
extension ViewController: MiniAppMessageDelegate {
func requestDevicePermission(permissionType: MiniAppDevicePermissionType, completionHandler: @escaping (Result<String, Error>) -> Void) {
switch permissionType {
case .location:
let locStatus = CLLocationManager.authorizationStatus()
switch locStatus {
case .authorizedAlways, .authorizedWhenInUse:
completionHandler(.success("allowed"))
}
}
}
API Docs: MiniAppMessageDelegate
SDK has its own implementation to show the list of requested custom permissions. If you want to display your own UI for requesting custom permissions, you can do it by overriding the method like below,
extension ViewController: MiniAppMessageDelegate {
func requestCustomPermissions(
permissions: [MASDKCustomPermissionModel],
miniAppTitle: String,
completionHandler: @escaping (
Result<[MASDKCustomPermissionModel], Error>) -> Void) {
completionHandler(.success(permissions))
}
MiniApp iOS SDK supports list of Custom Permissions ( MiniAppCustomPermissionType
) and these can be stored and retrieved using the following public interfaces.
Custom permissions and its status can be retrieved using the following interface. getCustomPermissions
will return list of MASDKCustomPermissionModel
that contains the meta-info such as title and its granted status.
let miniAppPermissionsList = MiniApp.shared().getCustomPermissions(forMiniApp: miniAppId)
Custom permissions for a Mini App is cached by the SDK and you can use the following interface to store and retrieve it when you need.
MiniApp.shared().setCustomPermissions(forMiniApp: String, permissionList: [MASDKCustomPermissionModel])
API Docs: MiniAppShareContentDelegate
By default, Mini App iOS SDK can open its own controller for content sharing. If you want to override this, you just have to implement the shareContent(info: MiniAppShareContent, completionHandler: @escaping (Result<MASDKProtocolResponse, Error>) -> Void)
from MiniAppShareContentDelegate
, which is part of MiniAppMessageDelegate
.
extension ViewController: MiniAppMessageDelegate {
func shareContent(info: MiniAppShareContent,
completionHandler: @escaping (
Result<String, Error>) -> Void) {
let activityController = UIActivityViewController(activityItems: [info.messageContent], applicationActivities: nil)
present(activityController, animated: true, completion: nil)
completionHandler(.success("SUCCESS"))
}
}
API Docs: MiniAppAdDisplayDelegate
Mini App SDK gives you the possibility to display ads triggered by your Mini App from your host app. There are 2 ways to achieve this:
- by implementing MiniAppAdDisplayDelegate by yourself
- if you rely on Google ads to display your ads you can simply implement
pod MiniApp/Admob
(Admob 7.+) orpod MiniApp/Admob8
(Admob 8.+) into your pod dependencies (see settings section).
When you chose to implement Google Ads support for your Mini Apps (see configuration section), you must provide an AdMobDisplayer
as adsDelegate parameter when creating your Mini App display:
Be careful when declaring your variable, as Mini App SDK does not keep a strong reference to it, it is preferable to declare it as a global variable or in most cases it will become nil once your method called.
let adsDisplayer = AdMobDisplayer() // This is just declared here as a convenience for the example.
// notice the new parameter for ads delegation
let params = MiniAppParameters.default(
config: MiniAppConfig(config: Config.current(), adsDisplayer: adsDisplayer, messageInterface: self),
appId: "your-miniapp-id"
)
let miniAppView = MiniAppView(params: params)
If you chose to implement ads displaying by yourself, you first need do implement MiniAppAdDisplayDelegate
and provide it to a MiniAppAdDisplayer
.
For the same reasons mentioned in AdMobDisplayer
section above, prefer declaring your MiniAppAdDisplayer
globally.
class ViewController: UIViewController {
let adsDisplayer: MiniAppAdDisplayer
override func viewDidLoad() {
super.viewDidLoad()
adsDisplayer = MiniAppAdDisplayer(with: self) // you must provide your ads displayer a MiniAppAdDisplayDelegate
}
}
extension ViewController: MiniAppAdDisplayDelegate {
func loadInterstitial(for adId: String, onLoaded: @escaping (Result<Void, Error>) -> Void) {
// Here your code to load and prepare an interstitial ad
let isLoaded = onInterstitiaLoaded()
if isLoaded {
onLoaded(.success(()))
} else {
onLoaded(.failure(NSError("Custom interstitial failed loading")))
}
}
func showInterstitial(for adId: String, onClosed: @escaping (Result<Void, Error>) -> Void) {
// Here your code to display an interstitial ad
var interstitialController = getCustomInsterstialController(for: adId, onClosed: onClosed)
}
func loadRewarded(for adId: String, onLoaded: @escaping (Result<Void, Error>) -> Void) {
// Here your code to load and prepare an ad
let isLoaded = onRewardedAdLoaded()
if isLoaded {
onLoaded(.success(()))
} else {
onLoaded(.failure(NSError("Custom rewarded ad failed loading")))
}
}
func showRewarded(forId: String, onClosed: @escaping (MiniAppReward?) -> Void, onFailed: @escaping (Error) -> Void) {
// Here your code to display your rewarded ad.
// When the onClosed closure is called the user receives a reward you defined
var interstitialController = getCustomRewrdedController(for: adId, onClosed: onClosed, reward: MiniAppReward(type: "star", amount: 100))
}
}
Once the delegate implemented, don't forget to provide it when you call a Mini App creation with the parameter adsDelegate
:
let params = MiniAppParameters.default(
config: MiniAppConfig(config: Config.current(), adsDisplayer: adsDisplayer, messageInterface: self),
//...
)
API Docs: MiniAppUserInfoDelegate
Get the User profile related details using 'MiniAppMessageDelegate'. The following delegates/interfaces will be called only if the user has allowed respective Custom permissions
Retrieve user name of the User
extension ViewController: MiniAppMessageDelegate {
func getUserName(completionHandler: @escaping (Result<String, MASDKError>) -> Void) {
// Implementation to return the User name
completionHandler(.success(""))
}
}
Retrieve Profile Photo of the User
extension ViewController: MiniAppMessageDelegate {
func getProfilePhoto(completionHandler: @escaping (Result<String?, MASDKError>) -> Void) {
// Implementation to return the Profile photo URI
completionHandler(.success(""))
}
}
Retrieve the Contact list of the User
extension ViewController: MiniAppMessageDelegate {
func getContacts(completionHandler: @escaping (Result<[MAContact]?, MASDKError>) -> Void) {
// Implementation to return the contact list
completionHandler(.success([]))
}
}
Retrieve access token and expiry date
extension ViewController: MiniAppMessageDelegate {
func getAccessToken(miniAppId: String,
scopes: MASDKAccessTokenPermission?,
completionHandler: @escaping (Result<MATokenInfo, MASDKCustomPermissionError>) -> Void) {
completionHandler(.success(.init(accessToken: "ACCESS_TOKEN", expirationDate: Date())))
}
}
API Docs: ChatMessageBridgeDelegate
Send a message to a contact from the user profile contacts list using 'MiniAppMessageDelegate' methods. Three methods can be triggered by the Mini App, and here are the recommended behaviors for each one:
sendMessageToContact(_:completionHandler:) |
sendMessageToContactId(_:message:completionHandler:) |
sendMessageToMultipleContacts(_:completionHandler:) |
|
---|---|---|---|
Triggered when | Mini App wants to send a message to a contact. | Triggered when Mini App wants to send a message to a specific contact. | Triggered when Mini App wants to send a message to multiple contacts. |
Contact chooser needed | single contact | None | multiple contacts |
Action | send the message to the chosen contact | send a message to the specified contactId without any prompt to the User | send the message to all chosen contacts |
On success | invoke completionHandler success with the ID of the contact which was sent the message. | invoke completionHandler success with the ID of the contact which was sent the message. | invoke completionHandler success with a list of IDs of the contacts which were successfully sent the message. |
On cancellation | invoke completionHandler success with nil value. | invoke completionHandler success with nil value. | invoke completionHandler success with nil value. |
On error | invoke completionHandler error when there was an error. | invoke completionHandler error when there was an error. | invoke completionHandler error when there was an error. |
Here is an example of integration:
extension ViewController: MiniAppMessageDelegate {
public func sendMessageToContact(_ message: MessageToContact, completionHandler: @escaping (Result<String?, MASDKError>) -> Void) {
presentContactsPicker { controller in
controller.message = message
controller.title = NSLocalizedString("Pick a contact", comment: "")
}
}
public func sendMessageToContactId(_ contactId: String, message: MessageToContact, completionHandler: @escaping (Result<String?, MASDKError>) -> Void) {
getContacts { result in
switch result {
case success(let contacts):
if let contact = contacts.first(where: { $0.id == contactId }) {
// insert here code to send the message
completionHandler(.success(contact.id))
} else {
fallthrough
}
default:
completionHandler(.failure(.invalidContactId))
}
}
}
public func sendMessageToMultipleContacts(_ message: MessageToContact, completionHandler: @escaping (Result<[String]?, MASDKError>) -> Void) {
presentContactsPicker { chatContactsSelectorViewController in
chatContactsSelectorViewController.contactsHandlerJob = completionHandler
chatContactsSelectorViewController.message = message
chatContactsSelectorViewController.multipleSelection = true
chatContactsSelectorViewController.title = NSLocalizedString("Select contacts", comment: "")
}
}
func presentContactsPicker(controllerPresented: (() -> Void)? = nil, contactsPickerCreated: (ChatContactsSelectorViewController) -> Void) {
if let viewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "ChatContactsSelectorViewController") as? ChatContactsSelectorViewController {
UINavigationController.topViewController()?.present(UINavigationController(rootViewController: viewController), animated: true, completion: controllerPresented)
}
}
}
Retrieve Rakuten points (standard, term, cash) of the User. It's necessary to allow the Rakuten Points
custom permission for retrieving the points.
extension ViewController: MiniAppMessageDelegate {
func getPoints(completionHandler: @escaping (Result<MAPoints, MASDKPointError>) -> Void) {
// Implementation to return points
completionHandler(.success(MAPoints(standard: 500, term: 400, cash: 300)))
}
}
Support to download files of base64 urls.
It's necessary to allow the File Download
custom permission to make file downloads available.
Allows MiniApps to store data safely in a key/value store.
Secure Storage will be initalized when MiniAppSecureStorage.set
is called the first time. After initialization the storage will be automatically lazy loaded when the MiniApp is opened.
You can specify the max storage file size in MiniAppSdkConfig.storageMaxSizeInBytes
when creating a MiniApp.
When no maximum storage size is set the default value will be 2_000_000 (2Mb). Exceeding the file size limit will
throw an error MiniAppSecureStorageError.storageFullError
when setting new values into the storage.
MiniAppSdkConfig(
//...
// set the max size to 5 Mb
storageMaxSizeInBytes: 5_000_000
)
Use wipeSecureStorages
to delete all secure storages stored on the device.
MiniAppSecureStorage.wipeSecureStorages()
You can receive the message string/json from MiniApp in to host app trough the universal bridge interface implemented in MiniAppMessageDelegate
.
To receive the message from the MiniApp and process it in the host app you must implement the delegeate method sendJsonToHostApp(info:completionHandler:)
from the MiniAppMessageDelegate.
extension ViewController: MiniAppMessageDelegate {
func sendJsonToHostApp(info: String, completionHandler: @escaping (Result<MASDKProtocolResponse, UniversalBridgeError>) -> Void) {
print("Message form MiniApp: \(info)")
completionHandler(.success(.success))
}
}
API Docs: MiniApp.list
MiniApp library calls are done via the MiniApp.shared()
singleton with or without a MiniAppSdkConfig
instance (you can get the current one with Config.current()
). If you don't provide a config instance, values in custom iOS target properties will be used by default.
MiniApp.shared().list { (result) in
...
}
or
MiniApp.shared(with: Config.current()).list { (result) in
...
}
API Docs: MiniApp.info
MiniApp.shared().info(miniAppId: miniAppID) { (result) in
...
}
or
MiniApp.shared(with: Config.current()).info(miniAppId: miniAppID) { (result) in
...
}
MiniApp developers can define several metadata into the manifest.json
:
required
&optional
permissions- access token audience/scope permissions
- custom variables/items inside
customMetaData
.
Host app will use the defined interfaces to retrieve these details from manifest.json
{
"reqPermissions":[
{
"name":"rakuten.miniapp.user.USER_NAME",
"reason":"Describe your reason here."
},
{
"name":"rakuten.miniapp.user.PROFILE_PHOTO",
"reason":"Describe your reason here."
}
],
"optPermissions":[
{
"name":"rakuten.miniapp.user.CONTACT_LIST",
"reason":"Describe your reason here."
},
{
"name":"rakuten.miniapp.device.LOCATION",
"reason":"Describe your reason here."
}
],
"accessTokenPermissions":[
{
"audience":"rae",
"scopes":["idinfo_read_openid", "memberinfo_read_point"]
},
{
"audience":"api-c",
"scopes":["your_service_scope_here"]
}
],
"customMetaData":{
"hostAppRandomTestKey":"metadata value"
}
}
You can retrieve the meta-data of a MiniApp using the following method,
MiniApp.shared().getMiniAppManifest(miniAppId: miniAppId,
miniAppVersion: miniAppVersionId,
languageCode: NSLocale.current.languageCode) { (result) in
switch result {
case .success(let manifestData):
// Retrieve the custom key/value pair like the following.
let randomTestKeyValue = manifestData.customMetaData?["hostAppRandomTestKey"]
case .failure:
break
}
...
}
By passing the languageCode
in the above getMiniAppManifest
method, you can get the localized description/reason for the permission from the platform API.
If there is no localized description/reason is available, it will return the default value given in the manifest.json
SDK internally checks if the User has responded to the list of required/optional permissions that are enforced by the Mini App. So host app SHOULD make sure that the user is prompted with necessary custom permissions and get the response back from the user.
Before calling the MiniApp.create
, host app should make sure the following things are done:
- Retrieve the meta-data for the Mini App using getMiniAppManifest
- Display/Prompt the list of required & optional permissions to the user and the user response should be stored using MiniApp.setCustomPermissions
- Call MiniApp.create to start downloading the Mini App
How to get downloaded Mini App meta-data
In Host App, we can get the downloaded manifest information as following:
let downloadedManifest = MiniApp.shared().getDownloadedManifest(miniAppId:)
HostApp can compare the old downloadedManifest
and the latest manifest by calling MiniApp.shared().getMiniAppManifest to detect any new changes.
API Docs: MiniApp.listDownloadedWithCustomPermissions
Gets the list of downloaded Mini apps info and associated custom permissions status
MiniApp.shared().listDownloadedWithCustomPermissions()
API Docs: MiniAppSdkConfig
Along with Mini app features, Mini app SDK does provides more customization for the user. Some of the more customizable features are below,
Every call to the API can be done with default parameters retrieved from the project .plist configuration file, or by providing a MiniAppSdkConfig
object during the call. Here is a simple example class we use to create the configuration in samples below:
class Config: NSObject {
class func current() -> MiniAppSdkConfig {
MiniAppSdkConfig(
baseUrl: "https://your.custom.url",
rasProjectId: "your_RAS_Project_id",
subscriptionKey: "your_subscription_key",
hostAppVersion: "your_custom_version",
isPreviewMode: true,
analyticsConfigList: [MAAnalyticsConfig(acc: "477", aid: "998")],
requireMiniAppSignatureVerification: true,
sslKeyHash: MiniAppConfigSSLKeyHash(
pin: "AABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=",
backup: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
)
}
}
NOTE: RMAHostAppUserAgentInfo
cannot be configured at run time.
Mini App SDK localization is based on iOS framework localizable strings system.
In the following table, you can find all the keys that the SDK is using to translate its texts to your UI language.
Mini App SDK will look in the host app Localizable.strings
for these keys to find local texts. If these keys are not present, a default text is provided.
Localization key | Usage | Default text | Parameters |
---|---|---|---|
miniapp.sdk.ios.alert.title.ok |
alert dialogs validation button | OK | - |
miniapp.sdk.ios.alert.title.cancel |
alert dialogs cancellation button | Cancel | - |
miniapp.sdk.ios.ui.allow |
permission screen authorization validation | Allow | - |
miniapp.sdk.all.ui.save |
permission screen denial validation | Save | - |
miniapp.sdk.ios.firstlaunch.footer |
used at the bottom of the permissions validation screen | %@[1] wants to access the above permissions. Choose your preference accordingly.\n\n You can also manage these permissions later in the Miniapp settings | [1] Mini App name |
miniapp.sdk.ios.error.message.server |
error reporting (decription) | Server returned an error. %@[1]: %@[2] | [1] Error code [2] Error message |
miniapp.sdk.ios.error.message.invalid_url |
error reporting (decription) | URL is invalid. | - |
miniapp.sdk.ios.error.message.invalid_app_id |
error reporting (decription) | Provided Mini App ID is invalid. | - |
miniapp.sdk.ios.error.message.invalid_version_id |
error reporting (decription) | Provided Mini App Version ID is invalid. | - |
miniapp.sdk.ios.error.message.invalid_contact_id |
error reporting (decription) | Provided contact ID is invalid. | - |
miniapp.sdk.ios.error.message.invalid_response |
error reporting (decription) | Invalid response received from server. | - |
miniapp.sdk.ios.error.message.download_failed |
error reporting (decription) | Failed to download the mini app. | - |
miniapp.sdk.ios.error.message.miniapp_meta_data_required_permisions_failure |
error reporting (decription) | Mini App has not been granted all of the required permissions. | - |
miniapp.sdk.ios.error.message.unknown |
error reporting (decription) | Unknown error occurred in %@[1] domain with error code %@[2]: %@[3] | [1] Error domain [2] Error code [3] Error message |
miniapp.sdk.ios.error.message.host_app |
error reporting (domain) | Host app Error | - |
miniapp.sdk.ios.error.message.failed_to_conform_to_protocol |
error reporting (decription) | Host app failed to implement required interface | - |
miniapp.sdk.ios.error.message.no_published_version |
error reporting (decription) | Server returned no published versions for the provided Mini App ID. | - |
miniapp.sdk.ios.error.message.miniapp_id_not_found |
error reporting (decription) | Server could not find the provided Mini App ID. | - |
miniapp.sdk.ios.error.message.unknown_server_error |
error reporting (decription) | Unknown server error occurred | - |
miniapp.sdk.ios.error.message.ad_not_loaded |
error reporting (decription) | Ad %@[1] is not loaded yet | [1]Ad id |
miniapp.sdk.ios.error.message.ad_loading |
error reporting (decription) | Previous %@[1] is still in progress | [1]Ad id |
miniapp.sdk.ios.error.message.ad_loaded |
error reporting (decription) | Ad %@[1] is already loaded | [1]Ad id |
If you need to use one of this strings in your host application, you can use the convenience method MASDKLocale.localize(_:_:)
API Docs: MiniAppNavigationConfig
MiniApp iOS SDK provides a fully customizable way to implement a navigation interface inside your html pages with a MiniAppNavigationConfig
object. The class takes 3 arguments:
navigationBarVisibility
:- never = the UI will never be shown
- auto = navigation UI is only shown when a back or forward action is available
- always = navigation UI is always present
navigationDelegate
: A delegate that will receive MiniApp view instructions about available navigation options. It will also receive taps on external links.customNavigationView
: A view implementingMiniAppNavigationDelegate
that will be overlayed to the bottom of the MiniApp view
let navConfig = MiniAppNavigationConfig(
navigationBarVisibility: .always,
navigationDelegate: myNavigationDelegate,
customNavigationView: mCustomView)
MiniApp.shared(with: Config.current(), navigationSettings: navConfig).info(miniAppId: miniAppID) { (result) in
...
}
API Docs: MiniAppNavigationConfig
By default MiniApp iOS SDK will open external links into a separate modal controller when tapped. MiniAppNavigationDelegate
implements a method that allows to override this behaviour and provide your own external links management. Here is an example of implementation:
extension ViewController: MiniAppNavigationDelegate {
func miniAppNavigation(shouldOpen url: URL, with externalLinkResponseHandler: @escaping (URL) -> Void) {
// Getting your custom viewcontroller
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ExternalWebviewController") as? ExternalWebViewController {
viewController.currentURL = url
viewController.miniAppExternalUrlLoader = MiniAppExternalUrlLoader(webViewController: viewController, responseHandler: externalLinkResponseHandler)
self.presentedViewController?.present(viewController, animated: true)
}
}
...
}
The externalLinkResponseHandler
closure allows you to give a feedback as an URL to the SDK, for example when the controller is closed or when a custom scheme link is tapped. This closure can be passed to a MiniAppExternalUrlLoader
object that will provide a method to test an URL and return the appropriate decision for a WKNavigationDelegate
method, and if you provided a controller it will be dismissed automatically. Here is an example following previous example:
extension ExternalWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.currentURL = self.webView.url
}
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(miniAppExternalUrlLoader?.shouldOverrideURLLoading(navigationAction.request.url) ?? .allow)
}
}
You can choose to give orientation lock control to Mini Apps. However, this requires you to add some code to your AppDelegate
which could have an affect on your entire App. If you do not wish to do this, please see the section "Allow only full screen videos to change orientation".
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if window?.isKeyWindow != true {
return .all
}
if MiniApp.MAOrientationLock.isEmpty {
return .all
} else {
return MiniApp.MAOrientationLock
}
}
You can add the following if you want to enable videos to change orientation. Note that if you do not wish to add code to AppDelegate
as in the above example, you can still allow videos inside the Mini App to use landscape mode even when your App is locked to portrait mode and vice versa.
import AVKit
extension AVPlayerViewController {
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if MiniApp.MAOrientationLock.isEmpty {
return .all
} else {
return MiniApp.MAOrientationLock
}
}
}
MiniApp iOS SDK sends some notification to your app when some events are triggered by a MiniApp:
- When it is launched
- When it is closed
To catch this events and retrieve insight data, you simply have to register to the notification center like this:
NotificationCenter.default.addObserver(self, selector: #selector(yourMethod(_:)), name: MiniAppAnalytics.notificationName, object: nil)
@objc func yourMethod(_ notification:Notification) {
if let payload = notification.object as? [String:String] {
// do something with the data
}
}
Here is an example of data contained in payload:
{
"etype": "click",
"actype": "mini_app_open",
"cp": {
"mini_app_project_id": "1234",
"mini_app_id": "4321",
"mini_app_version_id": "123456"
}
}
While creating a Mini App, you can pass the optional query parameter as well. This query parameter will be appended to the Mini App's URL.
For eg.,
let params = MiniAppParameters.default(
config: MiniAppConfig(config: Config.current(), messageInterface: self),
appId: "your-miniapp-id",
queryParams: "param1=value1¶m2=value2"
)
let miniAppView = MiniAppView(params: params)
self.view.addSubview(miniAppView)
miniAppView.frame = self.view.bounds
miniAppView.load { success in
// ...
}
And the Mini App will be loaded like the following scheme,
mscheme.rakuten//miniapp/index.html?param1=value1¶m2=value2
Mini App SDK requires the host app to include the following set of device permissions into its Info.plist file:
Plist key | Permission | Reason |
---|---|---|
NSLocationAlwaysAndWhenInUseUsageDescription | Location | Mini app to track/get the current location of the user |
NSCameraUsageDescription | Camera | Camera permission required by Mini app to take pictures |
NSMicrophoneUsageDescription | Microphone | Microphone permission required by Mini app to record a video. |
Mini App SDK allows MiniApps to react to several events triggered by host app. These include
Event | Reason |
---|---|
pause |
MiniApp view controller will disappear, MiniApp will open an external web view, host application will resign to be active |
resume |
MiniApp view controller did appear, user closed a web view launched by MiniApp, host application did become active |
externalWebViewClosed |
user closed a web view launched by MiniApp |
Load Mini-app from cache directly using the following approach,
let miniAppView = MiniAppView(params: params)
miniAppView.load(fromCache: true) { success in
// ...
}
fromCache
helps to retrieve the already downloaded mini-app from the cache.
NOTE: Using the above approach will never retrieve/query latest version of the mini-app.
Send JSON or a string conent to mini app from the host app using the below sdk api, This api will send the content message/json to the mini app using the custom events which will must be listned by the mini apps to receive the content.
miniAppView.sendJsonToMiniApp(string: "<Your string content / json content>")
string
This will hold the Json or stringcontent to be sent to MiniApp from the host app.
NOTE: Using the above approach will never retrieve/query latest version of the mini-app.
MiniApp SDK allows to send keyboard shown
and hidden
events.
MiniApp
.shared()
.keyboardShown(navigationBarHeight: 84, screenHeight: 814, keyboardheight: 350)
MiniApp
.shared()
.keyboardHidden(navigationBarHeight: 0, screenHeight: 0, keyboardheight: 0)
These events can be listened to by the JS SDK
window.addEventListener(MiniAppKeyboardEvents.KEYBOARDSHOWN, function (e) {
...
}
window.addEventListener(MiniAppKeyboardEvents.KEYBOARDHIDDEN, function (e) {
...
}
MiniApp can request the host app to close itself through the javascript bridge event trigger.
MiniAppSDK in iOS provides the MiniAppMessageDelegate
interface method func closeMiniApp(withConfirmation: , completionHandler:)
which can be implemented in the host app. This delegate method is an optional.
extension ViewController: MiniAppMessageDelegate {
func closeMiniApp(withConfirmation: Bool, completionHandler: @escaping (Result<Bool, MiniAppJavaScriptError>) -> Void) {
if withConfirmation {
// You can handle the close action with confirmation alert.
} else {
// you can directly close the MiniApp without the confirmation alert.
}
}
}
MiniApp can request the host app to share the Themes primary and secondary colors through the javascript bridge event trigger.
MiniAppSDK in iOS provides the MiniAppMessageDelegate
interface method func getHostAppThemeColors(completionHandler: @escaping (Result<HostAppThemeColors?, MASDKError>) -> Void)
which can be implemented in the host app. This delegate method is an optional.
extension ViewController: MiniAppMessageDelegate {
func getHostAppThemeColors(completionHandler: @escaping (Result<HostAppThemeColors?, MASDKError>) -> Void) {
completionHandler(
.success(
HostAppThemeColors(
primaryColor: "#FF008C",
secondaryColor: "#7D64BE")
)
)
}
}
MiniApps can send the analytics information to host app through the sendAnalytics
interface defined in JS-SDK.
iOS Hostapp can extend this optional MAAnalayticsDelegate
interface/delegate to receive the analytics that is sent from the Miniapp.
extension ViewController: MAAnalyticsDelegate {
func didReceiveMAAnalytics(analyticsInfo: MAAnalyticsInfo, completionHandler: @escaping (Result<MASDKProtocolResponse, MAAnalyticsError>) -> Void) {
print(analyticsInfo)
// analyticsInfo: MAAnalyticsInfo - This object holds the Analytics info of the events triggered from MiniApps, This can be further used to record the MiniApp Analytics events from host app.
completionHandler(.success(.success))
}
}
Host app can check if Miniapp is downloaded and cached already using the following interface
if MiniApp.isMiniAppCacheAvailable(appId: miniAppId, versionId: versionId) {
// Returns true if Miniapp is available
}
If you want to have deep links directly to your mini apps, then you must implement deep link handling within your App. This can be done using either a custom deep link scheme (such as myAppName://miniapp
) or a Universal Link (such as https://www.example.com/miniapp
). See the following resources for more information on how to implement deep linking capabilities:
After you have implemented deep linking capabilities in your App, then you can configure your deep link to open and launch a Mini App. Note that your deep link should contain information about which mini app ID to open. Also, you can pass query parameters and a URL fragment to the mini app. The recommended deep link format is similar to https://www.example.com/miniapp/MINI_APP_ID?myParam=myValue#myFragment
where the myParam=myValue#myFragment
portion is optional and will be passed directly to the mini app.
The following is an example which will parse the mini app ID and query string from a deep link:
// In your AppDelegate
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = URLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return false
}
return handleDeepLink(components: components)
}
func handleDeepLink(components: URLComponents) -> Bool
{
guard let host = components.host, host == "example.com" {
return false
}
let pathComponents = components.path.split("/")
guard
let rootPath = pathComponents.first
else { return false }
if (rootPath == "miniapp") {
guard
let id = pathComponents[1]
else { return false }
let query = components.query ?? ""
let fragment = components.fragment ?? ""
let queryString = query + "#" + fragment
// Note that `myMiniAppCoordinator` is just a placeholder example for your own class
// Inside this class you should call `MiniApp.create` in order to create and display the mini app
myMiniAppCoordinator.goToMiniApp(miniAppId: id, query: queryString)
return true
}
return false
}
In the case that a user logs out of your App, you should clear the session data for all of your Mini Apps. This will ensure that the next user does not have access to the stored sensitive information about the previous user such as Local Storage, IndexedDB, and Web SQL.
The session data can be cleared by using the following:
WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
modifiedSince: Date.distantPast, completionHandler: {
// Data removal complete
})
Note: This will also clear the storage, cookies, and authentication data for ALL WkWebViews
used by your App.
See the full CHANGELOG.