@allboatsrise/expo-marketingcloudsdk 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.plugin.js CHANGED
@@ -1 +1 @@
1
- module.exports = require('./plugin/build/withMarketingCloudSdk')
1
+ module.exports = require('./plugin/build/index')
@@ -0,0 +1 @@
1
+ export * from 'react-native-marketingcloudsdk';
package/build/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from 'react-native-marketingcloudsdk';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC","sourcesContent":["export * from 'react-native-marketingcloudsdk';\n"]}
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "@allboatsrise/expo-marketingcloudsdk",
3
- "version": "0.0.1",
3
+ "author": "All Boats Rise Inc.",
4
+ "license": "MIT",
5
+ "version": "0.0.2",
4
6
  "description": "Expo module for Salesforce Marketing Cloud SDK",
5
7
  "homepage": "https://github.com/allboatsrise/expo-marketingcloudsdk",
6
8
  "keywords": [
@@ -17,8 +19,8 @@
17
19
  "type": "git",
18
20
  "url": "https://github.com/allboatsrise/expo-marketingcloudsdk.git"
19
21
  },
20
- "main": "build/MarketingCloudSdk.js",
21
- "types": "build/MarketingCloudSdk.d.ts",
22
+ "main": "build/index.js",
23
+ "types": "build/index.d.ts",
22
24
  "sideEffects": false,
23
25
  "files": [
24
26
  "/build/**/*",
@@ -40,12 +42,14 @@
40
42
  "preset": "expo-module-scripts"
41
43
  },
42
44
  "dependencies": {
43
- "@expo/config-plugins": "^4.0.2"
45
+ "@expo/config-plugins": "^4.0.0"
44
46
  },
45
47
  "devDependencies": {
46
- "expo-module-scripts": "^2.0.0"
48
+ "expo-module-scripts": "^2.0.0",
49
+ "react-native-marketingcloudsdk": "^7.4.3"
47
50
  },
48
51
  "peerDependencies": {
52
+ "@expo/config-plugins": "^4.0.0",
49
53
  "expo": "*",
50
54
  "react-native-marketingcloudsdk": "^7.4.3"
51
55
  }
@@ -0,0 +1,2 @@
1
+ import { ExportedConfig } from "@expo/config-plugins";
2
+ export declare function getGoogleServicesFilePath(config: Pick<ExportedConfig, 'android'>, projectRoot: string): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getGoogleServicesFilePath = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function getGoogleServicesFilePath(config, projectRoot) {
10
+ var _a;
11
+ const partialSourcePath = (_a = config.android) === null || _a === void 0 ? void 0 : _a.googleServicesFile;
12
+ if (!partialSourcePath) {
13
+ throw new Error(`googleServicesFile property not set in expo config`);
14
+ }
15
+ const completeSourcePath = path_1.default.resolve(projectRoot, partialSourcePath);
16
+ if (!fs_1.default.existsSync(completeSourcePath)) {
17
+ throw new Error(`File not found at ${completeSourcePath}`);
18
+ }
19
+ return completeSourcePath;
20
+ }
21
+ exports.getGoogleServicesFilePath = getGoogleServicesFilePath;
@@ -0,0 +1,3 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ import { MarketingCloudSdkPluginProps } from '../types';
3
+ export declare const withAndroidConfig: ConfigPlugin<MarketingCloudSdkPluginProps>;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.withAndroidConfig = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const config_plugins_1 = require("@expo/config-plugins");
9
+ const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
10
+ const helpers_1 = require("./helpers");
11
+ const withAndroidConfig = (config, props) => {
12
+ // 1. Add Marketing Cloud SDK repository
13
+ config = withConfigureRepository(config, props);
14
+ // 2. Provide FCM credentials
15
+ // Configure manually via Expo's googleServicesFile property.
16
+ // @see https://stackoverflow.com/a/63109187
17
+ // 3. Configure the SDK in your MainApplication.java class
18
+ config = withConfigureMainApplication(config, { ...props });
19
+ return config;
20
+ };
21
+ exports.withAndroidConfig = withAndroidConfig;
22
+ const withConfigureRepository = (config) => {
23
+ return (0, config_plugins_1.withAppBuildGradle)(config, async (config) => {
24
+ config.modResults.contents = (0, generateCode_1.mergeContents)({
25
+ src: config.modResults.contents,
26
+ newSrc: ` implementation 'com.salesforce.marketingcloud:marketingcloudsdk:${'8.0.4'}'`,
27
+ anchor: /dependencies\s?{/,
28
+ offset: 1,
29
+ tag: '@allboatsrise/expo-marketingcloudsdk(maven:dependencies)',
30
+ comment: '//'
31
+ }).contents;
32
+ return config;
33
+ });
34
+ };
35
+ const withConfigureMainApplication = (config, props) => {
36
+ return (0, config_plugins_1.withMainApplication)(config, async (config) => {
37
+ var _a;
38
+ let senderId = props.senderId;
39
+ if (!senderId) {
40
+ const googleServicesFilePath = (0, helpers_1.getGoogleServicesFilePath)(config, config.modRequest.projectRoot);
41
+ const googleServices = JSON.parse(fs_1.default.readFileSync(googleServicesFilePath, 'utf8'));
42
+ senderId = (_a = googleServices === null || googleServices === void 0 ? void 0 : googleServices.project_info) === null || _a === void 0 ? void 0 : _a.project_number;
43
+ if (!senderId) {
44
+ throw new Error(`Failed to extract sender id from google services file. (path: ${googleServicesFilePath})`);
45
+ }
46
+ }
47
+ config.modResults.contents = (0, generateCode_1.mergeContents)({
48
+ src: config.modResults.contents,
49
+ newSrc: ` MarketingCloudSdk.init(this,
50
+ MarketingCloudConfig.builder()
51
+ .setApplicationId(${JSON.stringify(props.appId)})
52
+ .setAccessToken(${JSON.stringify(props.accessToken)})
53
+ .setSenderId(${JSON.stringify(senderId)})
54
+ .setMarketingCloudServerUrl(${JSON.stringify(props.serverUrl)})
55
+ .setNotificationCustomizationOptions(NotificationCustomizationOptions.create(R.drawable.ic_notification))
56
+ .setAnalyticsEnabled(${props.analyticsEnabled ? 'true' : 'false'})
57
+ .build(this),
58
+ initializationStatus -> Log.e("INIT", initializationStatus.toString())
59
+ );`,
60
+ anchor: /super\.onCreate\(\);/,
61
+ offset: 1,
62
+ tag: '@allboatsrise/expo-marketingcloudsdk(onCreate)',
63
+ comment: '//'
64
+ }).contents;
65
+ return config;
66
+ });
67
+ };
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
3
8
  const config_plugins_1 = require("@expo/config-plugins");
4
- const withMarketingCloudSdkAndroid_1 = require("./withMarketingCloudSdkAndroid");
5
- const withMarketingCloudSdkIOS_1 = require("./withMarketingCloudSdkIOS");
6
- const pkg = require('@allboatsrise/expo-marketingcloudsdk/package.json');
9
+ const android_1 = require("./android");
10
+ const ios_1 = require("./ios");
7
11
  const ERROR_PREFIX = 'Marketing Cloud SDK Plugin:';
8
12
  const withMarketingCloudSdk = (config, props) => {
9
13
  if (!props) {
@@ -19,8 +23,9 @@ const withMarketingCloudSdk = (config, props) => {
19
23
  if (!accessToken) {
20
24
  throw new Error(`${ERROR_PREFIX} Must provide access token.`);
21
25
  }
22
- config = (0, withMarketingCloudSdkAndroid_1.withMarketingCloudSdkAndroid)(config, { ...props, serverUrl, appId, accessToken });
23
- config = (0, withMarketingCloudSdkIOS_1.withMarketingCloudSdkIOS)(config, { ...props, serverUrl, appId, accessToken });
26
+ config = (0, android_1.withAndroidConfig)(config, { ...props, serverUrl, appId, accessToken });
27
+ config = (0, ios_1.withIOSConfig)(config, { ...props, serverUrl, appId, accessToken });
24
28
  return config;
25
29
  };
30
+ const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(path_1.default.dirname(path_1.default.dirname(__dirname)), 'package.json'), 'utf8'));
26
31
  exports.default = (0, config_plugins_1.createRunOncePlugin)(withMarketingCloudSdk, pkg.name, pkg.version);
@@ -0,0 +1,3 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ import { MarketingCloudSdkPluginProps } from '../types';
3
+ export declare const withIOSConfig: ConfigPlugin<MarketingCloudSdkPluginProps>;
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.withIOSConfig = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const config_plugins_1 = require("@expo/config-plugins");
10
+ const Xcodeproj_1 = require("@expo/config-plugins/build/ios/utils/Xcodeproj");
11
+ const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
12
+ const withIOSConfig = (config, props) => {
13
+ config = withEntitlements(config, props);
14
+ config = withExtraAppDelegateProtocols(config, props);
15
+ config = withConfiguration(config, props);
16
+ config = withPushConfiguration(config, props);
17
+ config = withDelegateImplementation(config, props);
18
+ return config;
19
+ };
20
+ exports.withIOSConfig = withIOSConfig;
21
+ const withEntitlements = (config, { mode = 'development' }) => {
22
+ return (0, config_plugins_1.withEntitlementsPlist)(config, async (config) => {
23
+ config.modResults['aps-environment'] = mode;
24
+ return config;
25
+ });
26
+ };
27
+ const withExtraAppDelegateProtocols = (config) => {
28
+ return (0, config_plugins_1.withDangerousMod)(config, [
29
+ 'ios',
30
+ async (config) => {
31
+ const appDelegateHeaderFile = path_1.default.join(config.modRequest.platformProjectRoot, (0, Xcodeproj_1.getProjectName)(config.modRequest.projectRoot), 'AppDelegate.h');
32
+ let headerContent = fs_1.default.readFileSync(appDelegateHeaderFile, 'utf8');
33
+ ['UIApplicationDelegate', 'UNUserNotificationCenterDelegate'].forEach(protocol => {
34
+ if (headerContent.match(new RegExp(`${protocol}`)))
35
+ return config;
36
+ headerContent = headerContent.replace(/(EXAppDelegateWrapper\s*<.*?RCTBridgeDelegate.*?)(>)/, `$1, ${protocol}$2`);
37
+ });
38
+ fs_1.default.writeFileSync(appDelegateHeaderFile, headerContent);
39
+ return config;
40
+ }
41
+ ]);
42
+ };
43
+ const withConfiguration = (config, props) => {
44
+ return (0, config_plugins_1.withAppDelegate)(config, async (config) => {
45
+ config.modResults.contents = (0, generateCode_1.mergeContents)({
46
+ src: config.modResults.contents,
47
+ newSrc: `#import <MarketingCloudSDK/MarketingCloudSDK.h>`,
48
+ anchor: /#import "AppDelegate\.h"/,
49
+ offset: 1,
50
+ tag: '@allboatsrise/expo-marketingcloudsdk(header)',
51
+ comment: '//'
52
+ }).contents;
53
+ config.modResults.contents = (0, generateCode_1.mergeContents)({
54
+ src: config.modResults.contents,
55
+ newSrc: ` MarketingCloudSDKConfigBuilder *mcsdkBuilder = [MarketingCloudSDKConfigBuilder new];
56
+ [mcsdkBuilder sfmc_setApplicationId:@${JSON.stringify(props.appId)}];
57
+ [mcsdkBuilder sfmc_setAccessToken:@${JSON.stringify(props.accessToken)}];
58
+ [mcsdkBuilder sfmc_setAnalyticsEnabled:@(${props.analyticsEnabled ? 'YES' : 'NO'})];
59
+ [mcsdkBuilder sfmc_setMarketingCloudServerUrl:@${JSON.stringify(props.serverUrl)}];
60
+
61
+ NSError *error = nil;
62
+ BOOL success = [[MarketingCloudSDK sharedInstance] sfmc_configureWithDictionary:[mcsdkBuilder sfmc_build] error:&error];`,
63
+ anchor: /\[super application:application didFinishLaunchingWithOptions:launchOptions\];/,
64
+ offset: -1,
65
+ tag: '@allboatsrise/expo-marketingcloudsdk(configuration)',
66
+ comment: '//'
67
+ }).contents;
68
+ return config;
69
+ });
70
+ };
71
+ const withPushConfiguration = (config, props) => {
72
+ return (0, config_plugins_1.withAppDelegate)(config, async (config) => {
73
+ config.modResults.contents = (0, generateCode_1.mergeContents)({
74
+ src: config.modResults.contents,
75
+ newSrc: ` if (success == YES) {
76
+ dispatch_async(dispatch_get_main_queue(), ^{
77
+ if (@available(iOS 10, *)) {
78
+ // set the UNUserNotificationCenter delegate - the delegate must be set here in
79
+ // didFinishLaunchingWithOptions
80
+ [UNUserNotificationCenter currentNotificationCenter].delegate = self;
81
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
82
+
83
+ [[UNUserNotificationCenter currentNotificationCenter]
84
+ requestAuthorizationWithOptions:UNAuthorizationOptionAlert |
85
+ UNAuthorizationOptionSound |
86
+ UNAuthorizationOptionBadge
87
+ completionHandler:^(BOOL granted, NSError *_Nullable error) {
88
+ if (error == nil) {
89
+ if (granted == YES) {
90
+ dispatch_async(dispatch_get_main_queue(), ^{
91
+ });
92
+ }
93
+ }
94
+ }];
95
+ } else {
96
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
97
+ UIUserNotificationSettings *settings = [UIUserNotificationSettings
98
+ settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound |
99
+ UIUserNotificationTypeAlert
100
+ categories:nil];
101
+ [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
102
+ #endif
103
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
104
+ }
105
+ });
106
+ } else {
107
+ // MarketingCloudSDK sfmc_configure failed
108
+ os_log_debug(OS_LOG_DEFAULT, "MarketingCloudSDK sfmc_configure failed with error = %@",
109
+ error);
110
+ }`,
111
+ anchor: /\[super application:application didFinishLaunchingWithOptions:launchOptions\];/,
112
+ offset: 1,
113
+ tag: '@allboatsrise/expo-marketingcloudsdk(push-configuration)',
114
+ comment: '//'
115
+ }).contents;
116
+ return config;
117
+ });
118
+ };
119
+ const withDelegateImplementation = (config, props) => {
120
+ return (0, config_plugins_1.withAppDelegate)(config, async (config) => {
121
+ config.modResults.contents = (0, generateCode_1.mergeContents)({
122
+ src: config.modResults.contents,
123
+ newSrc: `
124
+ - (void)application:(UIApplication *)application
125
+ didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
126
+ [[MarketingCloudSDK sharedInstance] sfmc_setDeviceToken:deviceToken];
127
+ }
128
+
129
+ - (void)application:(UIApplication *)application
130
+ didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
131
+ os_log_debug(OS_LOG_DEFAULT, "didFailToRegisterForRemoteNotificationsWithError = %@", error);
132
+ }
133
+
134
+ // The method will be called on the delegate when the user responded to the notification by opening
135
+ // the application, dismissing the notification or choosing a UNNotificationAction. The delegate
136
+ // must be set before the application returns from applicationDidFinishLaunching:.
137
+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
138
+ didReceiveNotificationResponse:(UNNotificationResponse *)response
139
+ withCompletionHandler:(void (^)(void))completionHandler {
140
+ // tell the MarketingCloudSDK about the notification
141
+ [[MarketingCloudSDK sharedInstance] sfmc_setNotificationRequest:response.notification.request];
142
+
143
+ if (completionHandler != nil) {
144
+ completionHandler();
145
+ }
146
+ }
147
+
148
+ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
149
+ willPresentNotification:(UNNotification *)notification
150
+ withCompletionHandler:
151
+ (void (^)(UNNotificationPresentationOptions options))completionHandler {
152
+ NSLog(@"User Info : %@", notification.request.content.userInfo);
153
+ completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
154
+ UNAuthorizationOptionBadge);
155
+ }
156
+
157
+ // This method is REQUIRED for correct functionality of the SDK.
158
+ // This method will be called on the delegate when the application receives a silent push
159
+ - (void)application:(UIApplication *)application
160
+ didReceiveRemoteNotification:(NSDictionary *)userInfo
161
+ fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
162
+ [[MarketingCloudSDK sharedInstance] sfmc_setNotificationUserInfo:userInfo];
163
+
164
+ completionHandler(UIBackgroundFetchResultNewData);
165
+ }`.trim(),
166
+ anchor: /-\s*\(BOOL\)application:\(UIApplication\s*\*\)application\s*didFinishLaunchingWithOptions/,
167
+ offset: -1,
168
+ tag: '@allboatsrise/expo-marketingcloudsdk(user-notification-methods)',
169
+ comment: '//'
170
+ }).contents;
171
+ return config;
172
+ });
173
+ };
@@ -5,11 +5,19 @@ export declare type MarketingCloudSdkPluginProps = {
5
5
  accessToken: string;
6
6
  /** Marketing Cloud server url */
7
7
  serverUrl: string;
8
- /** Marketing Cloud FCM sender id */
8
+ /**
9
+ * (Android only) Marketing Cloud FCM sender id. Defaults to `project_info.project_number`
10
+ * defined in `android.googleServicesFile` (google-services.json) if defined.
11
+ */
9
12
  senderId?: string;
10
13
  /** Marketing Cloud mid */
11
14
  mid?: string;
12
15
  inboxEnabled?: boolean;
13
16
  locationEnabled?: boolean;
14
17
  analyticsEnabled?: boolean;
18
+ /**
19
+ * (iOS only) Environment of the app: either 'development' or 'production'. Defaults to 'development'.
20
+ * @default 'development'
21
+ */
22
+ mode?: 'development' | 'production';
15
23
  };
File without changes
@@ -1 +0,0 @@
1
- //# sourceMappingURL=MarketingCloudSdk.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MarketingCloudSdk.js","sourceRoot":"","sources":["../src/MarketingCloudSdk.ts"],"names":[],"mappings":"","sourcesContent":[""]}
@@ -1,3 +0,0 @@
1
- import { ConfigPlugin } from '@expo/config-plugins';
2
- import { MarketingCloudSdkPluginProps } from './types';
3
- export declare const withMarketingCloudSdkAndroid: ConfigPlugin<MarketingCloudSdkPluginProps>;
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withMarketingCloudSdkAndroid = void 0;
4
- const config_plugins_1 = require("@expo/config-plugins");
5
- const { addMetaDataItemToMainApplication, getMainApplicationOrThrow, } = config_plugins_1.AndroidConfig.Manifest;
6
- const withMarketingCloudSdkAndroid = (config, props) => {
7
- config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
8
- const mainApplication = getMainApplicationOrThrow(config.modResults);
9
- addMetaDataItemToMainApplication(mainApplication, 'com.sfmc.appId', props.appId);
10
- addMetaDataItemToMainApplication(mainApplication, 'com.sfmc.accessToken', props.accessToken);
11
- addMetaDataItemToMainApplication(mainApplication, 'com.sfmc.serverUrl', props.serverUrl);
12
- return config;
13
- });
14
- config_plugins_1.WarningAggregator.addWarningAndroid('expo-marketingcloudsdk', JSON.stringify(config_plugins_1.withMainApplication, undefined, 2));
15
- config = (0, config_plugins_1.withMainActivity)(config, (config) => {
16
- console.log(config);
17
- return config;
18
- });
19
- return config;
20
- };
21
- exports.withMarketingCloudSdkAndroid = withMarketingCloudSdkAndroid;
@@ -1,3 +0,0 @@
1
- import { ConfigPlugin } from '@expo/config-plugins';
2
- import { MarketingCloudSdkPluginProps } from './types';
3
- export declare const withMarketingCloudSdkIOS: ConfigPlugin<MarketingCloudSdkPluginProps>;
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withMarketingCloudSdkIOS = void 0;
4
- const config_plugins_1 = require("@expo/config-plugins");
5
- const withMarketingCloudSdkIOS = (config, props) => {
6
- config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
7
- config.modResults['SFMC_APP_ID'] = props.appId;
8
- config.modResults['SFMC_ACCESS_TOKEN'] = props.accessToken;
9
- config.modResults['SFMC_SERVER_URL'] = props.serverUrl;
10
- if (typeof props.analyticsEnabled === 'boolean')
11
- config.modResults['SFMC_ANALYTICS_ENABLED'] = props.analyticsEnabled ? 1 : 0;
12
- if (typeof props.inboxEnabled === 'boolean')
13
- config.modResults['SFMC_INBOX_ENABLED'] = props.inboxEnabled ? 1 : 0;
14
- if (typeof props.locationEnabled === 'boolean')
15
- config.modResults['SFMC_LOCATION_ENABLED'] = props.locationEnabled ? 1 : 0;
16
- if (props.mid)
17
- config.modResults['SFMC_MID'] = props.mid;
18
- if (props.senderId)
19
- config.modResults['SFMC_SENDER_ID'] = props.senderId;
20
- return config;
21
- });
22
- return config;
23
- };
24
- exports.withMarketingCloudSdkIOS = withMarketingCloudSdkIOS;