All Products
Search
Document Center

Mobile Platform as a Service:Function configuration

Last Updated:Aug 29, 2023

Support for Experience Edition and Non-Experience Edition

Enable function:

- (NSDictionary *)nebulaCustomConfig {
  return @{
    @"mr_experience_required":@"true" // The switch to distinguish between the experience version and the official version.
	};
}

Open the mini program experience/test version:

// Indicates whether the experience version or non-experience version is used in China Mobile.
NSDictionary *param = @{
  @"experienceFlag": @"1", // 0-no limit. All versions are processed. 1-only experience versions are processed. 2-only non-experience versions are processed. 
};
[MPNebulaAdapterInterface startTinyAppWithId:appId params:param];

Support mini program page return query dialog box

  1. Update to the latest baseline.

  2. Call related APIs during mini program development.

    // Enable
    my.enableAlertBeforeUnload({
          message: 'Are you sure you want to leave this page?',
    });
    
    // Disable basic retouching.
    my.disableAlertBeforeUnload()

Real Machine Debug /Preview

// This debug url comes from the IDE scan code parsing, or copy and paste it from the IDE.
NSString* debug = @"mpaas://platformapi/xxxxx";
[MPNebulaAdapterInterface startDebugTinyAppWithUrl:debug];

Custom Title Bar

Navigation Bar Title Alignment

  • By default, the title of the mini program page of the SDK is displayed on the left.

  • If you need to center the display, you can set the following switches.

// set configDelegate in the beforeDidFinishLaunchingWithOptions method
@interface DTFrameworkInterface (XXX)<MPNebulaAdapterInterfaceConfigProtocol>

@end


// Set the proxy.
- (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    
    //. ... . 
	[MPNebulaAdapterInterface shareInstance].configDelegate = self;
}

// Implement the nebulaCustomConfig protocol method and set the following switches
- (NSDictionary *)nebulaCustomConfig
{
	return @{@"h5_tinyAppTitleViewAlignLeftConfig" : @"{\"enable\":\"NO\"}"};
}

Hide left and right buttons

If you want to globally hide the left and right buttons, you can override the leftBarButtonItem rightBarButtonItem of the current page in the viewwillAppear of the container base class.

- (void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear:animated];

    // Hide the left-side button.
    self.navigationItem.leftBarButtonItem = nil;
    self.navigationItem.leftBarButtonItems = nil;
    [self.navigationItem setHidesBackButton:YES];
    
    // Hide the right button.
    self.navigationItem.rightBarButtonItem = nil;
    self.navigationItem.rightBarButtonItems = nil;

}

Custom left and right buttons

If you want to globally modify the style and click behavior of the left and right buttons, you can override the leftBarButtonItems and rightBarButtonItems of the current page in the viewwillAppear of the container base class.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    // Customize the left-side button.
    UIButton *backBtn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 44, 44)];
    [backBtn setImage:[UIImage imageNamed:@"nav_back"] forState:UIControlStateNormal];
    [backBtn addTarget:self action:@selector(onClickBack) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:backBtn];
    self.navigationItem.leftBarButtonItems = @[item];
    
    // Customize the right-side button.
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onClickClose)];
}

- (void)onClickBack
{
	[self.navigationController popViewControllerAnimated:YES];
}

- (void)onClickClose
{
	[TASUtils exitTinyApplication:self.appId];
}
Note

To hide the default Back to Home button, you must implement the nebulaCustomConfig protocol method and set the following switch to hide the default Back to Home button.

- (NSDictionary *)nebulaCustomConfig
{
	return @{@"tiny_shouldHideBackToHomeBtn" : @"YES"};
}

Change the title font size

Recommended method: switch configuration.

The corresponding library is NebulaBiz.

- (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Modify the container switch value.
    [MPNebulaAdapterInterface shareInstance].configDelegate = self;
}

# pragma mark Modify the default switch value of the container
- (NSDictionary *)nebulaCustomConfig
{
    return @{
        @"tiny_mPaaSNavigationBarTitleFontSize": @"22",
        @"tiny_mPaaSNavigationBarTitleFontBold": @"YES",
    };
}

Custom mini program loading animation

For iOS mini programs, mPaaS allows developers to customize the content of the loading page. You can perform the following steps to configure the content:

  1. Inherit the subclass of APBaseLoadingView and customize the page view subclass. You can modify the style of the page view in the subclass.

    image.pngimage.png

    Sample code:

     @interface MPBaseLoadingView : APBaseLoadingView
     @end
     @implementation MPBaseLoadingView
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             self.backgroundColor = [UIColor grayColor];
             self.titleLabel.backgroundColor = [UIColor redColor];
             self.titleLabel.font = [UIFont boldSystemFontOfSize:8];
             self.iconImageView.backgroundColor = [UIColor blueColor];
             self.pageControl.backgroundColor = [UIColor orangeColor];
         }
         return self;
     }
     - (void)layoutSubviews
     {
         [super layoutSubviews];
         // Adjust the position of the view.
         CGSize size  = self.bounds.size;
         CGRect frame = CGRectMake((size.width - 80)/2, 0, 80, 80);
         self.iconImageView.frame = frame;
         frame = CGRectMake(15, CGRectGetMaxY(self.iconImageView.frame) + 6, size.width - 30, 22);
         self.titleLabel.frame = frame;
         frame = CGRectMake((size.width-40)/2, CGRectGetMaxY(self.titleLabel.frame) + 21, 40, 20);
         self.pageControl.frame = frame;
     }
     @end
  2. In the category of the DTFrameworkInterface class, override the baseloadViewClass method to return the custom loaded page View class name.

     - (NSString *)baseloadViewClass
     {
         return @"MPBaseLoadingView";
     }

Customize more menu bars

// Set the button (...) click event in the upper right corner of the mini program.
#define kNBInsideEvent_Scene_NavbarMenu_Right_Setting                 @"scene.navbarMenu.right.setting"

- (void)pluginDidLoad
{
    self.scope = kPSDScope_Service;
    
    [self.target addEventListener:kNBInsideEvent_Scene_NavbarMenu_Right_Setting withListener:self useCapture:NO];
    
    [super pluginDidLoad];
}

- (void)handleEvent:(RVKEvent *)event
{
    [super handleEvent:event];
    
    if ([event.eventType isEqualToString:kNBInsideEvent_Scene_NavbarMenu_Right_Setting]) {
        [event preventDefault];
        NSLog(@"Intercept the 3-point button click in the upper right corner, and your own panel will pop up here");
		}
}

Listener Interception Return

iOS has no physical return button.

// In the H5 base class:
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    // Customize the left-side button.
    UIButton *backBtn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 44, 44)];
    [backBtn setImage:[UIImage imageNamed:@"nav_back"] forState:UIControlStateNormal];
    [backBtn addTarget:self action:@selector(onClickBack) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:backBtn];
    self.navigationItem.leftBarButtonItems = @[item];
}

- (void)onClickBack
{
	[self.navigationController popViewControllerAnimated:YES];
}

Listener and Interception Disable

Custom close button:

// In the H5 base class:
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    // Customize the right-side button.
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onClickClose)];
}


- (void)onClickClose
{
	[TASUtils exitTinyApplication:self.appId];
}

Custom Appx loading animation (GIF only)

Configure the image URL in the nebulaCustomConfig. Example:

# Modify the default value of the pragma mark parameter.
- (NSDictionary *)nebulaCustomConfig
{
  return @{
	/// Other keys before are omitted here.
	@"tiny_customLoadingResourceURL": @"https://xxxx.xxxx/gif",
	@"tiny_customLoadingResourceSizeWidth": @"40", // The default value is 10
	@"tiny_customLoadingResourceSizeHeight": @"80", // The default value is 10
  };
}

Resource management

// Delete local mini programs. 
[[MPNebulaAdapterInterface shareInstance] clearAllAppInfo:appId];


// Obtain the information about all mini programs.
/**
 * Obtain the package information of a specified application.
 *
 * @param appId The array of application IDs.
 * @return NSDictionary Instances of all apps {appId:[NAMApp, ...], ...}
 */
[[MPNebulaAdapterInterface shareInstance] allAppsForAppId:@[@"xxx",@"xxx",@"xxx"]];

// Actively update all
[[MPNebulaAdapterInterface shareInstance] requestAllNebulaApps:^(NSDictionary *data, NSError *error) {
    NSLog(@"data[%@]", data[@"data"]);
}];

// Actively update a specific mini program.
[[MPNebulaAdapterInterface shareInstance] requestNebulaAppsWithParams:@{@"AppId":@"Version"} finish:^(NSDictionary *data, NSError *error) {
    NSLog(@"data[%@]", data[@"data"]);
}];

// Actively download the mini program.
[[MPNebulaAdapterInterface shareInstance] downLoadNebulaAppsWithParams:@{@"AppId":@"Version"}];

Preset mini program

Create an independent bundle. If DemoCustomPresetApps.bundle, add the AMR offline package and h5_json.json files downloaded from the releasing platform to this bundle. For more information, see:

image.png
Note

Currently, the publishing platform only supports downloading the h5_json.json configuration file of a single offline package. When multiple mini program packages are preset, you need to manually merge data from different h5_json.json into one configuration file.

The specific mini program program information is put into the nebula_preset.json, and the reference is as follows:

{
	"config":{
		"updateReqRate":16400,
		"limitReqRate":13600,
		"appPoolLimit":3,
		"versionRefreshRate":86400
	},
	"data":[
		{
			"app_desc":"Preset mini program",
			"app_id":"2022080915350001",
			"auto_install":1,
			"extend_info":{
				"launchParams":{
					"enableTabBar":"YES",
					"enableKeepAlive":"NO",
					"enableDSL":"YES",
					"nboffline":"sync",
					"enableWK":"YES",
					"page":"page/tabBar/component/index",
					"tinyPubRes":"YES",
					"enableJSC":"YES"
				},
				"usePresetPopmenu":"YES"
			},
			"fallback_base_url":"https://xxx/2022080915350001/1.0.1.0_all/nebula/fallback/",
			"global_pack_url":"",
			"installType":1,
			"main_url":"/index.html#page/tabBar/component/index",
			"name":"Preset mini program",
			"online":1,
			"package_url":"https://xxx/2022080915350001/1.0.1.0_all/nebula/2022080915350001_1.0.1.0.amr",
			"patch":"",
			"sub_url":"",
			"version":"1.0.1.0",
			"vhost":"https://2022080915350001.h5app.com"
		}
	],
	"resultCode":100,
	"resultMsg":"The operation is successful.",
	"state":"success"
}

When initializing the mini program, set the offline package path of the preset mini program to the bundle created in the previous step in the initNebulaWithCustomPresetApplistPath interface. Add the following code:

- (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Initialize RPC.
[MPRpcInterface initRpc];
// Initialize the container.
//    [MPNebulaAdapterInterface initNebula];
// Customize the JSAPI path and preset small package information.
NSString *presetApplistPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"DemoCustomPresetApps.bundle/h5_json.json"] ofType:nil];
NSString *appPackagePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"DemoCustomPresetApps.bundle"] ofType:nil];
NSString *pluginsJsapisPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"DemoCustomPlugins.bundle/Poseidon-UserDefine-Extra-Config.plist"] ofType:nil];
[MPNebulaAdapterInterface initNebulaWithCustomPresetApplistPath:presetApplistPath customPresetAppPackagePath:appPackagePath customPluginsJsapisPath:pluginsJsapisPath];
}

Start the mini program.

[MPNebulaAdapterInterface startTinyAppWithId:@"2020121720201217" params:nil];

Resource Load Interception

It is implemented by listening to the agent event kEvent_Proxy_Request_Start_Handler.

@implementation DemoPlugin4JSAPI
- (void)pluginDidLoad
{
    self.scope = kPSDScope_Scene; // 1
    
    [PSDProxy addEventListener:kEvent_Proxy_Request_Start_Handler withListener:self useCapture:NO];
    
    [super pluginDidLoad];
}

- (void)handleEvent:(PSDEvent *)event
{
   [super handleEvent:event];
    
    if ([event.eventType isEqualToString:kEvent_Proxy_Request_Start_Handler]) {

        PSDProxyEvent *proxyEvent = (PSDProxyEvent *)event;
        NSURLRequest *req = proxyEvent.request;
        NSString *path = [req.URL.path lowercaseString]; // The pah is the URL.
        NSLog(@"imageURL path[%d][%@]", arc4random(), path);
        NSString* imageURL = [req.URL absoluteString];
        NSLog(@"imageURL [%@]", imageURL);
        if ([imageURL hasSuffix:@".jpg"]) {
            // The description is a jpg image. Start preparing to go local:
            NSString* filePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"MPCustomPresetApps.bundle/testImage.png"]ofType:nil];
            if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
                NSData *fileData = [NSData dataWithContentsOfFile:filePath];
                if (fileData.length > 0) {
                    NSString *mimeType = [self getMimeTypeWithData:fileData type:@"image"];
                    [proxyEvent.customResponse setHeader:@"X-LocalRes" value:@"1"];
                    [proxyEvent.customResponse setHeader:@"max-age" value:@"86400"];
                    [proxyEvent.customResponse respondWithData:fileData mimeType:mimeType];
                }else{
                    [proxyEvent.customResponse respondWithData:nil mimeType:nil statusCode:404];
                }
            }
        }
    }    
}

- (int)priority
{
    return PSDPluginPriority_High +1;
}
@end

Signature verification

[MPNebulaAdapterInterface shareInstance].nebulaNeedVerify = YES;
NSString *keyPath = [[NSBundle mainBundle] pathForResource:@"h5_public_key" ofType:@"pem"];
[MPNebulaAdapterInterface shareInstance].nebulaPublicKeyPath = keyPath;

Start a mini program of a specified version

// The mini program can pull the package with the specified version number.
// Step 1: Delete the old appId cache:
//    [[MPNebulaAdapterInterface shareInstance] clearAllAppInfo:@"2021000000000013"];
// Step 2: Pass the specified version number
NSDictionary* dict = @{
    @"mPaasTinyAppTargetVersion" : @"1.0.176.0"
    };
[MPNebulaAdapterInterface startTinyAppWithId:@"2021000000000013" params:dict];

Custom JSAPI

Plist registration

Create a JSAPI class

  • Naming convention: To be consistent with the default plug-in name provided by the container, the name of the created JSAPI class starts with XXJsApi4, where XX is a custom prefix.

  • Base class: All JSAPI inherits from PSDJsApiHandler.

  • Implement the base method: In the .m file, you must override the method -(void)handler:context:callback:. When this JSAPI is called on the front end, it is forwarded to this method.

  • The parameters of this method have the following meanings:

    Parameter

    Description

    data

    The parameter passed in when the H5 page calls this JSAPI.

    context

    The context of the current H5 page. For more information, see PSDContext.h.

    callback

    Call the callback method after this JSAPI is completed and pass the call result to the H5 page in dictionary mode.

    The following code provides an example on how to call Java API operations for LindormTable SQL to access Lindorm wide tables:

      #import <NebulaPoseidon/NebulaPoseidon.h>
      @interface MPJsApiHandler4OpenSms : PSDJsApiHandler
      @end
      @implementation MPJsApiHandler4OpenSms
      - (void)handler:(NSDictionary *)data context:(PSDContext *)context callback:(PSDJsApiResponseCallbackBlock)callback
      {
      [super handler:data context:context callback:callback];
      // Open the system text message.
      NSURL *url = [NSURL URLWithString:@"sms://xxx"];
      BOOL reasult = [[UIApplication sharedApplication] openURL:url];
      callback(@{@"success":@(reasult)});
      }
      @end

Register JSAPI

Register this JSAPI in the custom Plist file.

  • Create a new Plsit file to manage custom JSAPI and Plugin. You can download the template file DemoCustomPlugins.bundle.zip and add it to your project.

  • Register the JSAPI class created in the previous step under the JsApis array:

    image.png

    The registered JSAPI is a dictionary type that contains the following two items:

    Name

    Description

    jsApi

    The name of the JSAPI operation called on the H5 page.

    Note

    To prevent the interaction between the custom JSAPI and the built-in JSAPI of the container from causing unavailability, add a prefix to the custom JSAPI name to distinguish it.

    name

    The name of the created JSAPI class.

Add code

You must also specify the path of the custom Plist file when you initialize the container configuration.

Initialize an H5 container. For more information, see Quick start.

The sample code is as following:

  - (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
  {  
      // Initialize the container.
      // [MPNebulaAdapterInterface initNebula];

      // Customize the JSAPI path and preset offline package information.
      NSString *presetApplistPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"DemoCustomPresetApps.bundle/h5_json.json"] ofType:nil];
      NSString *appPackagePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"DemoCustomPresetApps.bundle"] ofType:nil];
      NSString *pluginsJsapisPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"DemoCustomPlugins.bundle/Poseidon-UserDefine-Extra-Config.plist"] ofType:nil];
      [MPNebulaAdapterInterface initNebulaWithCustomPresetApplistPath:presetApplistPath customPresetAppPackagePath:appPackagePath customPluginsJsapisPath:pluginsJsapisPath]
  }

Enable the sharing feature

The Share button in the upper-right corner of the mini program is hidden by default. You can configure the following interface to display the Share button when the container is initialized:

Note

You must manually introduce the corresponding header file #import <TinyappService/TASUtils.h>.

  - (void)application:(UIApplication *)application afterDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      ...
      [TASUtils sharedInstance].shoulShowSettingMenu = YES;
      ...
  }

Customize the JsApi that implements shareTinyAppMsg, and accept the transparent parameters of the mini program page:

image.png

In the implementation class of the MPJsApi4ShareTinyAppMsg, you can obtain the parameters shared by the mini program page for business processing.

The sample code is as following:

  #import <NebulaPoseidon/NebulaPoseidon.h>
  @interface MPJsApi4ShareTinyAppMsg : PSDJsApiHandler
  @end
  #import "MPJsApi4ShareTinyAppMsg.h"
  #import <MessageUI/MessageUI.h>      
  @interface MPJsApi4ShareTinyAppMsg()<APSKLaunchpadDelegate>
  @property(nonatomic, strong) NSString *shareUrlString;
  @end
  @implementation MPJsApi4ShareTinyAppMsg
  - (void)handler:(NSDictionary *)data context:(PSDContext *)context callback:(PSDJsApiResponseCallbackBlock)callback
  {
      [super handler:data context:context callback:callback];
      NSString * appId = context.currentSession.createParam.expandParams[@"appId"];
      NSString * page = data[@"page"]?:@"";
      NSString * title = data[@"title"]?:@"";
      NSString * desc = data[@"desc"]?:@"";
      // Splice the shared content and call the sharing SDK.
      self.shareUrlString = [NSString stringWithFormat:@"http://appId=%@&page=%@&title=%@&desc=desc", appId, page, title, desc];
      [self openPannel];
  }
  - (void)openPannel {
      NSArray *channelArr = @[kAPSKChannelWeibo, kAPSKChannelWeixin, kAPSKChannelWeixinTimeLine, kAPSKChannelSMS, kAPSKChannelQQ, kAPSKChannelQQZone, kAPSKChannelDingTalkSession, kAPSKChannelALPContact, kAPSKChannelALPTimeLine];
      APSKLaunchpad *launchPad = [[APSKLaunchpad alloc] initWithChannels:channelArr sort:NO];
      launchPad.tag = 1000;
      launchPad.delegate = self;
      [launchPad showForView:[[UIApplication sharedApplication] keyWindow] animated:YES];
  }
  #pragma mark - APSKLaunchpadDelegate
  - (void)sharingLaunchpad:(APSKLaunchpad *)launchpad didSelectChannel:(NSString *)channelName {
      [self shareWithChannel:channelName tag:launchpad.tag];
      [launchpad dismissAnimated:YES];
  }
  - (void)shareWithChannel:(NSString *)channelName tag:(NSInteger)tag{
      APSKMessage *message = [[APSKMessage alloc] init];
      message.contentType = @"url";// The type is "text","image", and "url".
      message.content = [NSURL URLWithString:self.shareUrlString];
      message.icon = [UIImage imageNamed:@"MPShareKit.bundle/Icon_Laiwang@2x.png"];
      message.title = @"Here is the page title";
      message.de sc = @"Here is the description";
      APSKClient *client = [[APSKClient alloc] init];
      client.disableToastDisplay = YES;
      [client shareMessage:message toChannel:channelName completionBlock:^(NSError *error, NSDictionary *userInfo) {
          if(! error) {// Success
              [AUToast presentToastWithin:[[UIApplication sharedApplication] keyWindow]
                                 withIcon:AUToastIconSuccess
                                     text:@"The sharing is successful."
                                 duration:2
                                   logTag:@"demo"];
          } else {// Failed
              NSString *desc = error.localizedFailureReason.length > 0 ? error.localizedFailureReason : @"Sharing failed";
              [AUToast presentToastWithin:[[UIApplication sharedApplication] keyWindow]
                                 withIcon:AUToastIconNone
                                     text:desc
                                 duration:2
                                   logTag:@"demo"];
              NSLog(@"error = %@", error);
          }
      }];
  }
  @end

Permission pop-up window

// 1, mini program IDE
// The app.json file. Add the permission application under the window field. Currently, the following five applications are known. Other extensions as needed
"window": {
    "permission": {
      "album": "1 Album permission",
      "camera": "2 Camera permissions",
      "contact": "3 Address book permissions",
      "location": "4 Location permissions",
      "audio": "5 Microphone permissions"
    }
  },


// 2,Xcode transformation
// Fragment 1: Enable permission control.
- (void)application:(UIApplication *)application afterDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Omit other code:
    
    // Process the APIs related to permission extension here:
    // Manage mini program API permissions.
    [TAAuthorizeStorageManager shareInstance].authorizeAlertDelegate = self;
    
    // Other custom methods:
//    [TAAuthorizeStorageManager shareInstance].mPaaS_customKeyActionDict = @{
//        @"saveToCalendar" : @"calendar",
//    };
   
}


// Fragment 2: The key code to intercept is to determine whether the windowPermission field read from the mini program matches the OC:
# pragma mark mini program API permission control
- (void)showAlertWithTitle:(NSString *)title appName:(NSString *)appName storageKey:(NSString *)storageKey callback:(void (^)(NSInteger index))callback
{
    if ([title length] > 0) {
        // Use storageKey to obtain different data types.
        NSString* message = nil;
        NSDictionary* dict = [MPWindowPermissionDictCache sharedService].windowPermission;
        NSLog(@"dict allkeys[%@]", [dict allKeys]);
        NSLog(@"storageKey[%@]", storageKey);
        
        if ([storageKey containsString:@"camera"]) {
            NSString* camera = dict[@"camera"];
            message = camera;
        } else if ([storageKey containsString:@"album"]) {
            NSString* album = dict[@"album"];
            message = album;
        } else if ([storageKey containsString:@"location"]) {
            NSString* location = dict[@"location"];
            message = location;
        } else if ([storageKey containsString:@"contact"]) {
            NSString* contact = dict[@"contact"];
            message = contact;
        }
        
        
        // Permission blocking:
        if (![[dict allKeys] containsObject:storageKey]) {
            // How does the breakpoint get here? For example, the previous configuration was 5 permissions:
//            "permission": {
// "album": "1 Album permission",
// "camera": "2 Camera permissions",
// "contact": "3 Address book permissions",
// "location": "4 Location permissions",
// "audio": "5 Microphone permissions"
//            }
            // At this time, deliberately delete a permission, such as album , and change it to the following:
//            "permission": {
// "camera": "2 Camera permissions",
// "contact": "3 Address book permissions",
// "location": "4 Location permissions",
// "audio": "5 Microphone permissions"
//            }
// Then regenerate the QR code, let the mini program scan the code, and call jsapi:
//            saveImage() {
//                my.saveImage({
//                  url: 'https://img.alicdn.com/tps/TB1sXGYIFXXXXc5XpXXXXXXXXXX.jpg',
//                  showActionSheet: true,
//                  success: () => {
//                    my.alert({
// title: 'Successfully',
//                    });
//                  },
//                });
//              },
// The breakpoint will go here:
            NSLog(@"storageKey[%@] is not registered in app.json, directly rejects the call", storageKey);
            callback(0);
            return;
        }
        
        // Here are the custom permissions:
        if ([storageKey containsString:@"warning"]) {
            NSString* warning = dict[@"warning"];
            message = warning;
        }
        
        
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *hardAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            callback(1);
        }];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            callback(0); // Deny
        }];
        [alertController addAction:hardAction];
        [alertController addAction:cancelAction];
        UIViewController* aVC = DTContextGet().currentVisibleViewController;
        [aVC presentViewController:alertController animated:YES completion:nil];
    }
}

// Fragment 3:windowPermission assignment
@interface MPWindowPermissionDictCache : NSObject

@property(nonatomic, strong) NSDictionary* windowPermission;

+ (instancetype)sharedService;

@end

@implementation MPWindowPermissionDictCache


+ (instancetype)sharedService
{
    static MPWindowPermissionDictCache *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[MPWindowPermissionDictCache alloc] init];
    });
    
    return instance;
}

@end


// In the H5 base class, 
// @interface MPH5WebViewController : NXDefaultViewController;

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // The privacy protocol dialog box.
    NSDictionary*  windowPermission = self.options.windowPermission;
//    NSString *appId =self.options.appId;
    if (windowPermission) {
//        [mtu setObject:windowPermission forKey:appId];
        [MPWindowPermissionDictCache sharedService].windowPermission = windowPermission;
    }
}


Custom View

  1. Mini program code

    <view class="page">
      <view class="page-description"> I am an ordinary mini program view tag </view>
      <view class="page-section">
        <view class="page-section-demo" style="position: relative;">
    
          <view class="page-description"> Custom view tags </view>
          <mpaas-component
            id="mpaas-map"
            type="custom_map"
            onMpaasCustomEvent="onMpaasCustomEvent"
          />
          <button onTap="setColor">Set Color</button>
          
        </view>
      </view>
    </view>
    
  1. Register a custom view

    - (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
        // Customize view2.0
        NSString* name = @"mpaas-component";
        NSString* clsName = @"CustomTestView";
        [[PSDService sharedInstance] registerComponentWithName:name clsName:clsName];
    }
    
  1. Custom view implementation

    //
    //  CustomTestView.m
    //  TestCustomView
    //
    //
    
    #import "CustomTestView.h"
    
    @implementation CustomTestView
    
    - (id)initWithConfig:(NSDictionary *)config messageDelegate:(id<NBComponentMessageDelegate>)messageDelegate {
        self = [super initWithConfig:config messageDelegate:messageDelegate];
        if (self) {
            self.contentSpaceView = [[UIView alloc] init];
            self.contentSpaceView.backgroundColor = [UIColor orangeColor];
            self.contentSpaceView.frame = CGRectMake(0, 0, 100, 100);
            UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sendMessageToTinyApp)];
            [self.contentSpaceView addGestureRecognizer:tap];
            
            UILabel* lb = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 180, 40)];
            lb.text = @"I am a native UILabel of iOS";
            [self.contentSpaceView addSubview:lb];
        }
        return self;
    }
    
    // Return the view created in init.
     - (UIView *)contentView {
         return self.contentSpaceView;
     }
    
    // Appx2:
    -(void)componentReceiveMessage:(NSString *)message data:(NSDictionary *)data callback:(NBComponentCallback)callback {
    //    if ([message isEqualToString:@"setupMap"]) {
        if ([message isEqualToString:@"mpaasCustomEvent"]) {
            NSLog(@"The method of the mini program has been received");
            self.contentSpaceView.backgroundColor = [UIColor yellowColor];
            [self sendCustomMessageToMiniWithCallBackMethodName:@"onMpaasCustomEvent" paramData:@{
                    @"success": @(YES),
                    @"message": @"Change the color of Success"
                }];
        };
    }
    
    
    - (void)sendMessageToTinyApp {
        [self sendCustomMessageToMiniWithCallBackMethodName:@"onMpaasCustomEvent" paramData:@{}];
    }
    
    // appx2
    -(void)sendCustomMessageToMiniWithCallBackMethodName:(NSString *)methodName paramData:(NSDictionary *)data {
        NSDictionary *callBallData = data.count>0 ? data: @{};
        [self.nbComponentMessageDelegate sendCustomEventMessage:@"nbcomponent.mpaas-component.mpaasCustomEvent"
                                                      component:self
                                                           data:@{
                                                                    @"element": methodName,
                                                                    @"eventName": methodName,
                                                                    @"data": callBallData
                                                                }
                                                       callback:^(NSDictionary * _Nonnull data) {
            NSLog(@"callback data = %@", data);
        }];
    }
    
    
    // appx1
    //#pragma mark -Send a message to the mini program.
    //-(void)sendCustomMessageToMiniWithCallBackMethodName:(NSString *)methodName paramData:(NSDictionary *)data {
    //    NSDictionary *callBallData = data.count>0?data:@{};
    //    [self.nbComponentMessageDelegate sendCustomEventMessage:@"nbcomponent.mpaasComponent.customEvent" component:self data:@{@"element": @"dtk_map", @"eventName": methodName, @"data": callBallData} callback:^(NSDictionary * _Nonnull data) {
    //        NSLog(@"callback data = %@", data);
    //    }];
    //}
    //
    
    // The value of appx1.
    //-(void)componentReceiveMessage:(NSString *)message data:(NSDictionary *)data callback:(NBComponentCallback)callback {
    //    if ([message isEqualToString:@"setupMap"]) {
    // NSLog(@"The method of the mini program is received");
    //
    //        [self sendCustomMessageToMiniWithCallBackMethodName:@"onSetupMap" paramData:@{
    //                @"success": @(YES),
    //                @"message": @"setupMap Success"
    //            }];
    //    };
    //}
    
    
    @end
    

Mini program side implementation

//page.axml
<mpaas-component
          id="mpaas-barrage"
          type="custom_barrage" // The value of the type parameter. The value must be the same as the value of native.
          style="{{ width: 400, height: 200 }}" // You can only configure the width and height.
          onMpaasCustomEvent="onMpaasCustomEvent" // Receive native events
/>

//page.js

barrageContext = my.createMpaasComponentContext('mpaas-barrage');
// Send data to native.
barrageContext.mpaasCustomEvent({
        actionType: 'bindLivePlayer',
        data: {
          "id": "liveplayer",
          "barrages": ["Interesting", "Sofa", "Barrage 1", "Barrage 2", "Barrage 3"]
        }
      });
  }, 100) 
// The sample code for listening to events is as follows:
#import <AriverApp/RVAPluginBase.h>

NS_ASSUME_NONNULL_BEGIN

@interface DemoPlugin4APM : RVAPluginBase

@end

NS_ASSUME_NONNULL_END


#import "DemoPlugin4APM.h"
#import <NebulaPoseidon/PSDMonitorEvent.h>
#import <NBInside/NBInside.h>

@interface DemoPlugin4APM()

@property (nonatomic) CFTimeInterval appStartTimestamp;

@end

@implementation DemoPlugin4APM


- (void)pluginDidLoad
{
    self.scope = kPSDScope_Service;
    [self.target addEventListener:kEvent_Monitor_Log_Before withListener:self useCapture:NO];
    
    [self.target addEventListener:kEvent_Session_Create withListener:self useCapture:NO];
    [self.target addEventListener:kEvent_Page_Load_Complete withListener:self useCapture:NO];
    
    [self.target addEventListener:kNBInsideEvent_Scene_NavbarMenu_Right_Setting withListener:self useCapture:NO];
    
    [self.target addEventListener:@"mpaas.scene.navbarMenu.left.home" withListener:self useCapture:NO];
    
    [super pluginDidLoad];
}

- (void)handleEvent:(RVKEvent *)event
{
    [super handleEvent:event];
    
    if ([event.eventType isEqualToString:@"mpaas.scene.navbarMenu.left.home"]) {
        [event preventDefault];
        NSLog(@"Intercepted home return...");
    }

    if ([event.eventType isEqualToString:kNBInsideEvent_Scene_NavbarMenu_Right_Setting]) {
        [event preventDefault];
        NSLog(@"Intercept the 3-point button click in the upper right corner, and your own panel will pop up here");
        PSDEvent *oldEvent = (PSDEvent *)event;
        H5WebViewController *h5WebVC = (H5WebViewController *)[oldEvent.context currentViewController];
        WKWebView *webview = (WKWebView *)h5WebVC.psdContentView;
//        [webview evaluateJavaScript:@"window.AlipayJSBridge && AlipayJSBridge.call('my.redirectTo', {})" completionHandler:^(id _Nullable res, NSError * _Nullable error) {
        [webview evaluateJavaScript:@"window.AlipayJSBridge && AlipayJSBridge.call('alert', {})" completionHandler:^(id _Nullable res, NSError * _Nullable error) {
            NSLog(@"res = %@",res);

        }];
        return;
//

        UIAlertController *vc = [UIAlertController alertControllerWithTitle:@"Share and return to home page" message:nil preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *share = [UIAlertAction actionWithTitle:@"Share" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            PSDView *psdView = h5WebVC.psdView;
            // Set the value to onShare.
//            NSDictionary *params = @{@"page":[h5WebVC.url absoluteString]};
//            [psdView.page.bridge callJsApi:@"shareTinyAppMsg" url:[h5WebVC.url absoluteString] data:params responseCallback:^(id responseData) {
//                NSLog(@"responseData[%@]", responseData);
//            }];
        }];
        
        UIAlertAction *backhome = [UIAlertAction actionWithTitle:@"Back to Home" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            NSDictionary* expandParams = event.context.currentSession.createParam.expandParams;
            NSLog(@"expandParams[%@]", expandParams);
            NSString *appId = expandParams[@"appId"];
            
//            [h5WebVC callHandler:@"onBackHomeClick" data:@{@"useNativeShare":@YES} responseCallback:nil];
            
            [h5WebVC callHandler:@"redirectTo" data:@{@"url":@"/pages/index/index"} responseCallback:^(id responseData) {
                NSLog(@"callHandler: [%@]", responseData);
            }];
            
        }];
        
        
        UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        }];
        [vc addAction:share];
        [vc addAction:backhome];
        [vc addAction:cancel];
        [h5WebVC presentViewController:vc animated:YES completion:nil];
    }
    // The startup duration.
    // Calculation rule: the amount of time that is consumed from AppStart to PageLoad. Unit: seconds. 
    // The service value should only be calculated for the first callback, that is, the home page is loaded (the difference can be seen from the log of h5WebVC.url below)
    if ([event.eventType isEqualToString:kEvent_Session_Create]) {
        // Remember the timestamp:
        CFTimeInterval tm = CACurrentMediaTime(); 
        self.appStartTimestamp = tm;
        NSLog(@"kEvent_Session_Create: AppStart [%f]", tm);
    }

    if ([event.eventType isEqualToString:kEvent_Page_Load_Complete]) {
        PSDEvent *oldEvent = (PSDEvent *)event;
        H5WebViewController *h5WebVC = (H5WebViewController *)[oldEvent.context currentViewController];
        NSString* currentUrl = [h5WebVC.url absoluteString];
        NSLog(@"kEvent_Session_Create: page[%@]", currentUrl);
        if ([currentUrl containsString:@"#"]) { //# is used here to avoid multiple callbacks. The first page has a# number, so it is used as an identifier to judge
            // Timestamp:
            CFTimeInterval tm = CACurrentMediaTime();
            NSLog(@"kEvent_Session_Create: PageLoad [%f]", tm);
            CFTimeInterval appStartTime = tm - self.appStartTimestamp;
            NSLog(@"kEvent_Session_Create: AppStart-PageLoad=start time [%f]", appStartTime);
        }
    }
    
    if ([kEvent_Monitor_Log_Before isEqualToString:event.eventType]){
        
        PSDMonitorEvent *mEvent = (PSDMonitorEvent *)event;
        NSArray *params = [mEvent.params isKindOfClass:[NSArray class]]? mEvent.params : @[];
        NSString *bizType = [NSString stringWithFormat:@"%@", mEvent.bizType];
        NSString *seedId = [NSString stringWithFormat:@"%@", mEvent.seedId];
        if ([params count] != 4 || ![bizType length]) {
            return;
        }
        
        NSString* loadTime = @"";
        
        
        NSString *aplogstr = [NSString stringWithFormat:@"%@\nbizType=%@,param1=%@,param2=%@,param4=%@",params[2],bizType,params[0],params[1],params[3]];
        NSLog(@"seed seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        // Open exception: reproduce the path, open the demo and click "open exception H5_APP_PREPARE"
        if ([seedId isEqualToString:@"H5_APP_PREPARE"]) {
            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
            // For comparison test:
            if ([currentStep isEqualToString:@"noexistForce"]) {
                NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
            }
            // The errc! parameter. =1 indicates that the application is opened abnormally.
            NSString *errc = [self mp_valueFromLogStr:params[2] key:@"errc"];
            if ((errc != nil ) && ![errc isEqualToString:@"1"]) {
                NSLog(@"An open exception was caught:[%@]\nlog:[%@]", seedId, aplogstr);
            }

        }
        // js exception: reproduce the path, open the demo and click "APM mini program-> js error"
        else if ([seedId isEqualToString:@"H5_CUSTOM_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // White screen: reproduce the path. After the network is disconnected (in flight mode, WiFi is disconnected), open the demo and click "Open Exception H5_APP_PREPARE"
        else if ([seedId isEqualToString:@"H5_PAGE_ABNORMAL"]) {
            NSLog(@"Capture white screen:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // The pull package is abnormal. No path is available. The pull package can be reproduced only if the pull package interface is abnormal.
        else if ([seedId isEqualToString:@"H5_APP_REQUEST"]) {
            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
            NSLog(@"Pull package:[%@]\nlog[%@]:[%@]", seedId, currentStep, aplogstr);
            if ([currentStep containsString:@"fail"]) {
                NSLog(@"Package pulling exception:[%@]\nlog[%@]:[%@]", seedId, currentStep, aplogstr);
            }
        }
        // H5 _AL_NETWORK_PERMISSON_ERROR with restricted page access
        // Reproduce the path, open the demo and click "APM mini program-> page access restricted"
        else if ([seedId isEqualToString:@"H5_AL_PAGE_UNAUTHORIZED"] || [seedId isEqualToString:@"H5_AL_NETWORK_PERMISSON_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // Request exception / JSAPI exception H5 _AL_JSAPI_RESULT_ERROR
        // reproduce the path, open demo and click "APM mini program-> js api error"
        else if ([seedId isEqualToString:@"H5_AL_JSAPI_RESULT_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // An H5 resource request exception is _AL_NETWORK_PERFORMANCE_ERROR.
        // reproduce the path, network disconnection-> open demo and click "jump mini program-> point refresh"
        else if ([seedId isEqualToString:@"H5_AL_NETWORK_PERFORMANCE_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // The small package starts to be downloaded and the download is complete.
//        else if ([seedId isEqualToString:@"H5_APP_DOWNLOAD"]) {
//            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
// // start is the start
// // success indicates that the download is complete.
//            NSLog(@"seedId:[%@]step[%@]\nlog:[%@]", seedId, currentStep, aplogstr);
//        }
        
    }
    
    
    
    
}


# The pragma mark tool method, which is used to split parameters.
- (NSString *)mp_valueFromLogStr:(NSString *)logStr key:(NSString *)key
{
    if ( ![key isKindOfClass:[NSString class]] || ![key length] || ![logStr isKindOfClass:[NSString class]] || ![logStr length] || ![logStr containsString:key]) {
        return nil;
    }
    
    NSArray<NSString *> *arr = [logStr componentsSeparatedByString:@"^"];
    __block NSString *result = nil;
    
    [arr enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, __unused NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj hasPrefix:[NSString stringWithFormat:@"%@=", key]]) {
            NSArray *a = [obj componentsSeparatedByString:@"="];
            if ([a count] > 1) {
                result = a[1];
            }
            *stop = YES;
        }
    }];
    return  result;
}