MiniApp

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.

Features

  • Load MiniApp list
  • Load MiniApp metadata
  • Create a MiniApp view
  • 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

Requirements

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'

Getting started

Installation

Cocoapods

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'

Carthage

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"

Swift Package Manager

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: "4.1.0"))
]

Configuration

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)

Usage

Configure MiniApp


  1. Import the MiniApp SDK in your UIApplicationDelegate:
import MiniApp
  1. MiniApp.configure() should be always called at launch by AppDelegate.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    MiniApp.configure()
    // ...

    return true
}

Create a MiniApp for the given MiniAppId :


API Docs: MiniApp.create(appId:completionHandler:messageInterface:), MiniAppDisplayDelegate

MiniApp.create 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 MiniApp.create 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 return 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.

After calling MiniApp.create, you will obtain an instance of MiniAppDisplayDelegate which is the delegate of the Display module. You can call MiniAppDisplayDelegate.getMiniAppView to obtain a View for displaying the Mini App.

The following is a simplified example:

MiniApp.shared().create(appId: String, completionHandler: { (result) in
    switch result {
            case .success(let miniAppDisplay):
                let view = miniAppDisplay.getMiniAppView()
                view.frame = self.view.bounds
                self.view.addSubview(view)
            case .failure(let error):
                print("Error: ", error.localizedDescription)
            }
}, messageInterface: self)

Mini App Features


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 🚫
requestDevicePermission 🚫
requestCustomPermissions
shareContent
getUserName 🚫
getProfilePhoto 🚫
getAccessToken 🚫

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.

Retrieving Unique ID

API Docs: MiniAppUserInfoDelegate

extension ViewController: MiniAppMessageDelegate {
    func getUniqueId(completionHandler: @escaping (Result<String, MASDKError>) -> Void) {
        // Implementation to return the Unique ID
        completionHandler(.success(""))
    }
}

Requesting Location Permissions

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"))
            }
        }
    }

Custom Permissions

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))    
            }

Retrieving and storing Custom Permissions

MiniApp iOS SDK supports list of Custom Permissions ( MiniAppCustomPermissionType) and these can be stored and retrieved using the following public interfaces.

Retrieving the Mini App Custom Permissions using MiniAppID

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)

Store the Mini App Custom Permissions

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])

Share Mini app content

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"))
    }
}

Ads integration

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.+) or pod MiniApp/Admob8 (Admob 8.+) into your pod dependencies (see settings section). ###### Google ads displayer

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 adsDelegate = AdMobDisplayer() // This is just declared here as a convenience for the example.

MiniApp.shared(with: Config.current(), navigationSettings: Config.getNavConfig(delegate: self))
        .create(appId: appInfo.id, 
                version: appInfo.version.versionId,
                queryParams: getQueryParam(),
                completionHandler: { (result) in }, // retrieve your Mini App view in the result success 
                messageInterface: someMessageInterfaceDelegate, 
                adsDelegate: adsDelegate) // notice the new parameter for ads delegation
Custom ads displayer

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 adsDelegate: MiniAppAdDisplayer 

    override func viewDidLoad() {
      super.viewDidLoad()
      adsDelegate = 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:

MiniApp.shared(with: Config.current())
            .create(appId: appInfo.id,
                    version: appInfo.version.versionId,
                    queryParams: getQueryParam(),
                    completionHandler: { (result) in
            // Some code to manage Mini App view creation callbacks
        }, messageInterface: self, adsDelegate: self)

Retrieve User Profile details


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

User Name

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(""))
    }
}

Profile Photo

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(""))
    }
}

Contact List

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([]))
    }
}

Access Token Info

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())))
    }
}

Send message to contacts


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 Points

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)))
    }
}

File Download

Support to download files of base64 urls.
It’s necessary to allow the File Download custom permission to make file downloads available.

Load the MiniApp list:


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
    ...
}

Getting a MiniAppInfo :


API Docs: MiniApp.info

MiniApp.shared().info(miniAppId: miniAppID) { (result) in
    ...
}

or

MiniApp.shared(with: Config.current()).info(miniAppId: miniAppID) { (result) in
    ...
}

Mini App meta-data

Getting a MiniApp meta-data :


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

How to use MiniApp meta-data :


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:

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.

List Downloaded Mini apps


API Docs: MiniApp.listDownloadedWithCustomPermissions

Gets the list of downloaded Mini apps info and associated custom permissions status

 MiniApp.shared().listDownloadedWithCustomPermissions()

Advanced Features


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,

Overriding configuration on runtime

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.

Overriding localizations

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(_:_:)

Add a web navigation interface to the MiniApp view


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 implementing MiniAppNavigationDelegate 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)
    }
}

Orientation Lock

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”.

Allow Mini Apps to lock the view to any 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
        }
    }
Allow full screen videos to change orientation

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
        }
    }
}

Analytics events

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"
  }
}

Passing Query parameters while creating Mini App

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.,

MiniApp.shared().create(appId: String, queryParams: "param1=value1&param2=value2", completionHandler: { (result) in
    switch result {
            case .success(let miniAppDisplay):
                let view = miniAppDisplay.getMiniAppView()
                view.frame = self.view.bounds
                self.view.addSubview(view)
            case .failure(let error):
                print("Error: ", error.localizedDescription)
            }
}, messageInterface: self)

And the Mini App will be loaded like the following scheme,

mscheme.rakuten//miniapp/index.html?param1=value1&param2=value2

Permissions required from the Host app

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.

MiniApp events

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

Load Mini-app from cache directly using the following approach,

MiniApp.shared().create(appId: String, completionHandler: { (result) in
    switch result {
            case .success(let miniAppDisplay):
                let view = miniAppDisplay.getMiniAppView()
                view.frame = self.view.bounds
                self.view.addSubview(view)
            case .failure(let error):
                print("Error: ", error.localizedDescription)
            }
}, messageInterface: self, fromCache: true)

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.

Keyboard Events

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) {
    ...
}

FAQs and Troubleshooting

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
}

How do I clear the session data for Mini Apps?

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.

Changelog

See the full CHANGELOG.