├── .gitignore ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── chat │ └── rocket │ ├── RealPathUtil.java │ ├── ShareModule.java │ └── SharePackage.java ├── assets ├── NSExtensionActivationRule.png ├── android-demo.gif ├── ios-demo.gif ├── ios_step_01.png ├── ios_step_02.png ├── ios_step_03.png ├── ios_step_04.png ├── ios_step_05.png ├── ios_step_06.png ├── ios_step_07.png ├── ios_step_08.png ├── ios_step_09.png ├── ios_step_10.png ├── ios_step_11.png ├── ios_step_12.png ├── ios_step_13.png ├── ios_step_14.png ├── ios_step_15.png ├── ios_step_16.png └── ios_step_17.png ├── bin └── react-native-xcode.sh ├── ios ├── ReactNativeShareExtension.h ├── ReactNativeShareExtension.m └── ReactNativeShareExtension.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── ali.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── ali.xcuserdatad │ └── xcschemes │ ├── ReactNativeShareExtension.xcscheme │ └── xcschememanagement.plist ├── lib └── index.js ├── package.json └── rn-extensions-share.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | .tags 4 | .tags1 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ali Najafizadeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RN Share Extension 2 | 3 | This is a helper module which brings react native as an engine to drive share extension for your app. 4 | 5 | Fork from: [react-native-share-extension](https://github.com/alinz/react-native-share-extension) 6 | 7 |

8 | 9 | 10 |

11 | 12 | # Example 13 | 14 | [RN-Extensions-Share-Example](https://github.com/djorkaeffalexandre/share-extension) 15 | 16 | # Installation 17 | 18 | Installation should be very easy by just installing it from npm. 19 | 20 | ```js 21 | npm install rn-extensions-share --save 22 | ``` 23 | or 24 | ```js 25 | yarn add rn-extensions-share 26 | ``` 27 | 28 | # Setup 29 | 30 | The setup requires a little bit more work. I will try to describe as detail as possible. I would love to use `rnpm` so I will welcome pull request. 31 | 32 | ## iOS 33 | 34 | - Click on your project's name 35 | - Click on `+` sign 36 | 37 |

38 | 39 |

40 | 41 | - Select `Share Extension` under `iOS > Application Extension` 42 | 43 |

44 | 45 |

46 | 47 | - Select a name for your new share extension, in my case I chose `MyShareEx` 48 | 49 |

50 | 51 |

52 | 53 | - Delete both `ShareViewController.h` and `ShareViewController.m`. make sure to click on the `Move to Trash` button during deletion. 54 | 55 |

56 | 57 |

58 | 59 | - Create a new file under your share extension group, in my case it was `MyShareEx` 60 | 61 |

62 | 63 |

64 | 65 | - Make sure that the type of that object is `Objective-C File`, e.g. for `MyShareEx` name it `MyShareEx.m` 66 | 67 |

68 | 69 |

70 | 71 |

72 | 73 |

74 | 75 | - Since we deleted `ShareViewController.m`, we need to tell the storyboard of your share extension where the view needs to be loaded. So click on `MainInterface.storyboard` and replace the class field from `ShareViewController` to whatever you chose above (in my case `MyShareEx`) 76 | 77 |

78 | 79 |

80 | 81 | - Now it's time to add our library. Right click on the `Libraries` group and select `Add Files to "Sample1"...` 82 | 83 |

84 | 85 |

86 | 87 | - select `node_modules` > `rn-extensions-share` > `ios` > `ReactNativeShareExtension.xcodeproj` 88 | 89 |

90 | 91 |

92 | 93 | - Now we need to tell the share extension that we want to read new header files. Click on project name (in my case `Sample1`), then click on your extension name (in my case `MyShareEx`). After that click on Build Settings and search for `Header Search Paths` 94 | 95 |

96 | 97 |

98 | 99 | - Add the new path `$(SRCROOT)/../node_modules/rn-extensions-share/ios` with `recursive` selected 100 | 101 |

102 | 103 |

104 | 105 | - We need to add some linker flags as well, so search for `Other Linker Flags` and add `-ObjC` and `-lc++` 106 | 107 |

108 | 109 |

110 | 111 | - We also need to add all the static libraries such as `React` and `React Native Share Extension`. Select the `General` tab and under `Linked frameworks and Libraries` click on `+` and add all of the selected static binaries there 112 | 113 |

114 | 115 |

116 | 117 | - We need to modify the `Info.plist` inside the extension (e.g. `MyShareEx/Info.plist`) to make sure that our share extension can connect to internet. This is useful if you need your share extension connects to your API server or react-native remote server dev. For doing that we need to `App Transport Security Settings` to `Info.plist` 118 | 119 |

120 | 121 |

122 | 123 | - Now go back to your extension file (in my case `MyShareEx.m`) and paste the following code there **being sure to substitute `MyShareEx` in all three places for whatever you chose above** 124 | 125 | > If your project entry is `index.js` instead of `index.ios.js` then needs to replace `@"index.ios"` with `@"index"` 126 | 127 | ```objective-c 128 | #import 129 | #import "ReactNativeShareExtension.h" 130 | #import 131 | #import 132 | #import 133 | 134 | @interface MyShareEx : ReactNativeShareExtension 135 | @end 136 | 137 | @implementation MyShareEx 138 | 139 | RCT_EXPORT_MODULE(); 140 | 141 | - (UIView*) shareView { 142 | NSURL *jsCodeLocation; 143 | 144 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 145 | 146 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 147 | moduleName:@"MyShareEx" 148 | initialProperties:nil 149 | launchOptions:nil]; 150 | rootView.backgroundColor = nil; 151 | 152 | // Uncomment for console output in Xcode console for release mode on device: 153 | // RCTSetLogThreshold(RCTLogLevelInfo - 1); 154 | 155 | return rootView; 156 | } 157 | 158 | @end 159 | ``` 160 | 161 | # Set the NSExtensionActivationRule key in your Info.plist 162 | 163 | For the time being, this package only handles sharing of urls specifically from browsers. In order to tell the system to show your extension only when sharing a url, you must set the `NSExtensionActivationRule` key (under `NSExtensionAttributes`) in the share extension's Info.plist file as follows (this is also needed to pass Apple's reveiw): 164 | 165 | ``` 166 | NSExtensionAttributes 167 | 168 | NSExtensionActivationRule 169 | 170 | NSExtensionActivationSupportsWebURLWithMaxCount 171 | 1 172 | 173 | 174 | ``` 175 | 176 |

177 | 178 |

179 | 180 | Note that while the above will prevent many apps from wrongly sharing using your extension, some apps (e.g., YouTube) will still allow sharing using your extension, which might cause your extension to crash. Check out [this issue](https://github.com/alinz/react-native-share-extension/issues/40) for details. 181 | 182 | For reference about `NSExtensionActivationRule` checkout [Apple's docs](https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW8) 183 | 184 | 185 | - Try to build the project, it should now build successfully! 186 | 187 | 188 | ## Android 189 | 190 | - Edit `android/settings.gradle` and add the following 191 | 192 | ``` 193 | include ':app', ':rn-extensions-share' 194 | 195 | project(':rn-extensions-share').projectDir = new File(rootProject.projectDir, '../node_modules/rn-extensions-share/android') 196 | ``` 197 | 198 | - Edit `android/app/build.gradle` and add the following line before the react section in dependencies 199 | 200 | ``` 201 | dependencies { 202 | ... 203 | compile project(':rn-extensions-share') 204 | compile "com.facebook.react:react-native:+" 205 | } 206 | ``` 207 | 208 | - Create a folder called `share` under your java project and create two files. Call them `ShareActivity.java` and `ShareApplication.java`....just like your main project. 209 | 210 | - ShareActivity should look like this 211 | 212 | ```java 213 | // define your share project, if your main project is com.sample1, then com.sample1.share makes sense.... 214 | package com.sample1.share; 215 | 216 | 217 | // import ReactActivity 218 | import com.facebook.react.ReactActivity; 219 | 220 | 221 | public class ShareActivity extends ReactActivity { 222 | @Override 223 | protected String getMainComponentName() { 224 | // this is the name AppRegistry will use to launch the Share View 225 | return "MyShareEx"; 226 | } 227 | 228 | } 229 | ``` 230 | 231 | - ShareApplication should now look like this 232 | 233 | ```java 234 | // your package you defined in ShareActivity 235 | package com.sample1.share; 236 | // import build config 237 | import com.sample1.BuildConfig; 238 | 239 | import chat.rocket.SharePackage; 240 | 241 | import android.app.Application; 242 | 243 | import com.facebook.react.shell.MainReactPackage; 244 | import com.facebook.react.ReactNativeHost; 245 | import com.facebook.react.ReactApplication; 246 | import com.facebook.react.ReactPackage; 247 | 248 | import java.util.Arrays; 249 | import java.util.List; 250 | 251 | 252 | public class ShareApplication extends Application implements ReactApplication { 253 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 254 | @Override 255 | protected boolean getUseDeveloperSupport() { 256 | return BuildConfig.DEBUG; 257 | 258 | } 259 | 260 | @Override 261 | protected List getPackages() { 262 | return Arrays.asList( 263 | new MainReactPackage(), 264 | new SharePackage() 265 | ); 266 | } 267 | }; 268 | 269 | @Override 270 | public ReactNativeHost getReactNativeHost() { 271 | return mReactNativeHost; 272 | } 273 | } 274 | ``` 275 | 276 | - MainApplication should now look like this 277 | 278 | ```java 279 | // your package you defined in ShareActivity 280 | package com.sample1; 281 | 282 | import android.app.Application; 283 | import android.util.Log; 284 | 285 | import com.facebook.react.ReactApplication; 286 | import com.facebook.react.ReactInstanceManager; 287 | import com.facebook.react.ReactNativeHost; 288 | import com.facebook.react.ReactPackage; 289 | import com.facebook.react.shell.MainReactPackage; 290 | 291 | import chat.rocket.SharePackage; 292 | 293 | import java.util.Arrays; 294 | import java.util.List; 295 | 296 | public class MainApplication extends Application implements ReactApplication { 297 | 298 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 299 | @Override 300 | protected boolean getUseDeveloperSupport() { 301 | return BuildConfig.DEBUG; 302 | } 303 | 304 | @Override 305 | protected List getPackages() { 306 | return Arrays.asList( 307 | new MainReactPackage(), 308 | new SharePackage() 309 | ); 310 | } 311 | }; 312 | 313 | @Override 314 | public ReactNativeHost getReactNativeHost() { 315 | return mReactNativeHost; 316 | } 317 | } 318 | ``` 319 | 320 | - Edit `android/app/src/main/AndroidMainfest.xml` and add the new `activity` right after `devSettingActivity`. 321 | 322 | ```xml 323 | 324 | 325 | 332 | 333 | 334 | 335 | // for sharing links include 336 | 337 | // for sharing photos include 338 | 339 | // for all types 340 | 341 | 342 | 343 | ``` 344 | 345 | in this new `activity` I have used `@string/title_activity_share`, you can add those in `res/values`. 346 | 347 | So in `values/strings.xml` 348 | 349 | ```xml 350 | 351 | ... 352 | MyShareEx 353 | 354 | ``` 355 | 356 | - Now you should be able to compile the code without any errors! 357 | 358 | > If you need to add more packages to your share extension, do not override 359 | `getPackages`, instead override the `getMorePackages` method under `ShareExActivity`. 360 | 361 | # Share Component 362 | 363 | So both share extension and main application are using the same code base, or same main.jsbundle file. So the trick to separate Share and Main App is registering 2 different Component entries with `AppRegistry.registerComponent`. 364 | 365 | In both the iOS and Android share extensions we are telling react to load the extension component (in my case `MyShareEx`) from js. 366 | 367 | So in `index.ios.js` and `index.android.js` we are writing the same code: 368 | 369 | ```js 370 | //index.android.js 371 | import React from 'react' 372 | import { AppRegistry } from 'react-native' 373 | 374 | import App from './app.android' 375 | import Share from './share.android' 376 | 377 | AppRegistry.registerComponent('Sample1', () => App) 378 | AppRegistry.registerComponent('MyShareEx', () => Share) // TODO: Replace MyShareEx with my extension name 379 | ``` 380 | 381 | ```js 382 | //index.ios.js 383 | import React from 'react' 384 | import { AppRegistry } from 'react-native' 385 | 386 | import App from './app.ios' 387 | import Share from './share.ios' 388 | 389 | AppRegistry.registerComponent('Sample1', () => App) 390 | AppRegistry.registerComponent('MyShareEx', () => Share) // TODO: Replace MyShareEx with my extension name 391 | ``` 392 | 393 | So the `app.ios` and `app.android.js` refers to main app and `share.ios.js` and `share.android.js` refers to share extension. 394 | 395 | # Share Extension APIs 396 | 397 | - `data()` is a function that returns a promise. Once the promise is resolved, you get two values, `type` and `value`. 398 | 399 | 400 | ```js 401 | import ShareExtension from 'rn-extensions-share' 402 | ... 403 | 404 | const { type, value } = await ShareExtension.data(); // type = 'media' | 'text' 405 | ``` 406 | 407 | - `close()` 408 | 409 | Simply closes the share extension and returns the touch event back to application that triggered the share. 410 | 411 | # On iOS: Re-harvesting a shared image 412 | 413 | If your share extension is being used to process shared images (be it to social media or processing the image for information), `rn-extensions-share` will provide a URL within `value` with the location of the image. 414 | 415 | If you wish to pass this URL back down to Swift or Objective-C for whatever reason, you can use the following to convert the URL back into a UIImage: 416 | 417 | ```swift 418 | func harvestImage(from imageURL: String) { 419 | if let imgData = FileManager.default.contents(atPath: imageURL) { 420 | if let img = UIImage(data: data){ 421 | // Process image.. 422 | } 423 | } 424 | } 425 | ``` 426 | 427 | or in Objective-C: 428 | 429 | ```smalltalk 430 | -(void)harvestImage:(NSString *)imageURL { 431 | NSFileManager *fileManager = [NSFileManager defaultManager]; 432 | NSData *imgData = [fileManager contentsAtPath:imageURL]; 433 | UIImage img = [UIImage imageWithData:imgData]; 434 | // Process Image.. 435 | } 436 | ``` 437 | 438 | 439 | # Test on Device without dev-server 440 | 441 | Because a share extension in ios is treated as a separate container, they do not have access to main app folder. A resolution for this is that you have to build the script twice and package it inside the share extension container. The easiest way of doing this is create a `New Script Phase` in `Build Phases` of your share extension and copy the following line 442 | 443 | ```bash 444 | export NODE_BINARY=node 445 | ../node_modules/react-native/scripts/react-native-xcode.sh 446 | ``` 447 | 448 |

449 | 450 |

451 | 452 |

453 | 454 |

455 | 456 | 457 | # App and app extension bundles 458 | 459 | The app and app extension bundles can be shared or separated. Separating bundles allows for a minimal footprint for both app and app extension. 460 | 461 | #### plist key legend 462 | 463 | `BundleEntryFilename` - react-native index or shared index filename. 464 | 465 | `BundleSkipped` - Skips bundling when true. 466 | 467 | `BundleCopied` - Copies bundle instead of building when true. (Note: Should be set as true for share extension plist only when bundles are shared.) 468 | 469 | `BundleForced` - Forces bundling when true. 470 | 471 | ### Shared bundles 472 | 473 | The app extension target builds pre-loaded bundle and is copied to the app target. 474 | 475 | #### app plist values 476 | 477 | `BundleEntryFilename` = 'index.js' 478 | 479 | `BundleSkipped` = true 480 | 481 | `BundleCopied` = true 482 | 483 | #### app target's "Bundle React Native code and images" phase 484 | ``` 485 | export NODE_BINARY=node 486 | ../bin/react-native-xcode.sh 487 | ``` 488 | 489 | #### appShareExtension plist values 490 | 491 | `BundleEntryFilename` = 'index.js' 492 | 493 | `BundleForced` = true 494 | 495 | #### appShareExtension target's "Bundle React Native code and images" phase 496 | ``` 497 | cd ../ 498 | npm run cp-native-assets 499 | cd ios/ 500 | export NODE_BINARY=node 501 | ../bin/react-native-xcode.sh 502 | ``` 503 | 504 | ### Separated bundles 505 | 506 | The app extension and app targets build their own unique bundles. 507 | 508 | NSNotificationCenter will kill app extensions that are unable to free memory resources when receiving low memory warnings. Also, shared bundles introduce library/pod dependencies that aren't needed by both apps. Configuring separate bundles via Xcode requires customizing react-native-xcode.sh; a quick example customization can be found in the bin directory. Update the path to the packager in both the app and app extension target's "Bundle React Native code and images" Build Phases. 509 | 510 | Build time can be halved while debugging by disabling the bundle for whichever target you aren't debugging (app or app ex). 511 | 512 | #### app plist values 513 | 514 | `BundleEntryFilename` = 'index.js' 515 | 516 | #### app target's "Bundle React Native code and images" phase 517 | ``` 518 | export NODE_BINARY=node 519 | #export ENTRY_FILENAME=index 520 | ../bin/react-native-xcode.sh 521 | ``` 522 | 523 | #### appShareExtension plist values 524 | 525 | `BundleEntryFilename` = 'share.index.js' 526 | 527 | `BundleForced` = true 528 | 529 | #### appShareExtension target's "Bundle React Native code and images" phase 530 | ``` 531 | cd ../ 532 | npm run cp-native-assets 533 | cd ios/ 534 | export NODE_BINARY=node 535 | ../bin/react-native-xcode.sh 536 | ``` 537 | 538 | # Open container app 539 | Steps needed to open the host application from the share extension. 540 | 1) Allow your app to be opened via URL Scheme - [Learn more](https://medium.com/react-native-training/deep-linking-your-react-native-app-d87c39a1ad5e) 541 | 2) In xcode, select share extension and go to Build Settings and set **Require Only App-Extension-Safe API** to `NO`. 542 | 543 | Then you can open your app from the share extension by calling openURL: 544 | 545 | ``` 546 | import ShareExtension from 'rn-extensions-share'; 547 | 548 | ShareExtension.openURL('sample://example/url'); 549 | ``` 550 | 551 | # Troubleshooting on iOS devices 552 | 553 | Using the iOS Simulator and remote react-native debugger to develop the extension can hide issues that won't occur until testing on device. If you're experiencing issues running the extension on iOS devices, examine the Xcode console or device log for any obvious errors. If the Xcode console isn't receiving console output, ensure that the OS_ACTIVITY_MODE=disable environment var isn't enabled for the active scheme (see https://github.com/facebook/react-native/issues/10027). OS_ACTIVITY_MODE will hide device logging in the Xcode console, so its use is only advisable for iOS Simulator. For release mode, in order to view console output and see all output in the syslog, uncomment the `RCTSetLogThreshold(RCTLogLevelInfo - 1);` statement in your MyShareEx class. 554 | 555 | 1. If you're using react-native latest, error boundaries might help with JS errors. Another option is to catch render exceptions or test for errors, then render that output with something like a Text component. As long as your share app initializes, you should be able to see yellowbox/redbox errors. If you're not seeing them, you likely have an initialization issue. 556 | 2. Disable bundling on the main target when debugging the extension target, it's not needed when you're not working with the main app. 557 | 3. [Enable breaking on exceptions](http://blog.manbolo.com/2012/01/23/xcode-tips-1-break-on-exceptions). This is helpful if there are any exceptions in the extension itself; perhaps most useful if you've customized the native module. 558 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.1.3' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | 13 | android { 14 | compileSdkVersion 23 15 | buildToolsVersion "23.0.1" 16 | 17 | defaultConfig { 18 | minSdkVersion 16 19 | targetSdkVersion 22 20 | versionCode 1 21 | versionName "1.0" 22 | } 23 | lintOptions { 24 | abortOnError false 25 | } 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | implementation "com.facebook.react:react-native:+" 34 | } 35 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/chat/rocket/RealPathUtil.java: -------------------------------------------------------------------------------- 1 | // https://github.com/ivpusic/react-native-image-crop-picker/blob/master/android/src/main/java/com/reactnative/ivpusic/imagepicker/RealPathUtil.java 2 | package chat.rocket; 3 | 4 | import android.annotation.TargetApi; 5 | import android.content.ContentUris; 6 | import android.content.Context; 7 | import android.database.Cursor; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.os.Environment; 11 | import android.provider.DocumentsContract; 12 | import android.provider.MediaStore; 13 | 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | 19 | class RealPathUtil { 20 | @TargetApi(Build.VERSION_CODES.KITKAT) 21 | static String getRealPathFromURI(final Context context, final Uri uri) throws IOException { 22 | 23 | final boolean isKitKat = Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT; 24 | 25 | // DocumentProvider 26 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 27 | // ExternalStorageProvider 28 | if (isExternalStorageDocument(uri)) { 29 | final String docId = DocumentsContract.getDocumentId(uri); 30 | final String[] split = docId.split(":"); 31 | final String type = split[0]; 32 | 33 | if ("primary".equalsIgnoreCase(type)) { 34 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 35 | } else { 36 | final int splitIndex = docId.indexOf(':', 1); 37 | final String tag = docId.substring(0, splitIndex); 38 | final String path = docId.substring(splitIndex + 1); 39 | 40 | String nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag); 41 | if (nonPrimaryVolume != null) { 42 | String result = nonPrimaryVolume + "/" + path; 43 | File file = new File(result); 44 | if (file.exists() && file.canRead()) { 45 | return result; 46 | } 47 | return null; 48 | } 49 | } 50 | } 51 | // DownloadsProvider 52 | else if (isDownloadsDocument(uri)) { 53 | final String id = DocumentsContract.getDocumentId(uri); 54 | final Uri contentUri = ContentUris.withAppendedId( 55 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 56 | 57 | return getDataColumn(context, contentUri, null, null); 58 | } 59 | // MediaProvider 60 | else if (isMediaDocument(uri)) { 61 | final String docId = DocumentsContract.getDocumentId(uri); 62 | final String[] split = docId.split(":"); 63 | final String type = split[0]; 64 | 65 | Uri contentUri = null; 66 | if ("image".equals(type)) { 67 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 68 | } else if ("video".equals(type)) { 69 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 70 | } else if ("audio".equals(type)) { 71 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 72 | } 73 | 74 | final String selection = "_id=?"; 75 | final String[] selectionArgs = new String[] { 76 | split[1] 77 | }; 78 | 79 | return getDataColumn(context, contentUri, selection, selectionArgs); 80 | } 81 | } 82 | // MediaStore (and general) 83 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 84 | // Return the remote address 85 | if (isGooglePhotosUri(uri)) 86 | return uri.getLastPathSegment(); 87 | return getDataColumn(context, uri, null, null); 88 | } 89 | // File 90 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 91 | return uri.getPath(); 92 | } 93 | 94 | return null; 95 | } 96 | 97 | /** 98 | * If an image/video has been selected from a cloud storage, this method 99 | * should be call to download the file in the cache folder. 100 | * 101 | * @param context The context 102 | * @param fileName donwloaded file's name 103 | * @param uri file's URI 104 | * @return file that has been written 105 | */ 106 | private static File writeToFile(Context context, String fileName, Uri uri) { 107 | String tmpDir = context.getCacheDir() + "/rocket-chat"; 108 | Boolean created = new File(tmpDir).mkdir(); 109 | fileName = fileName.substring(fileName.lastIndexOf('/') + 1); 110 | File path = new File(tmpDir); 111 | File file = new File(path, fileName); 112 | try { 113 | FileOutputStream oos = new FileOutputStream(file); 114 | byte[] buf = new byte[8192]; 115 | InputStream is = context.getContentResolver().openInputStream(uri); 116 | int c = 0; 117 | while ((c = is.read(buf, 0, buf.length)) > 0) { 118 | oos.write(buf, 0, c); 119 | oos.flush(); 120 | } 121 | oos.close(); 122 | is.close(); 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | } 126 | return file; 127 | } 128 | 129 | /** 130 | * Get the value of the data column for this Uri. This is useful for 131 | * MediaStore Uris, and other file-based ContentProviders. 132 | * 133 | * @param context The context. 134 | * @param uri The Uri to query. 135 | * @param selection (Optional) Filter used in the query. 136 | * @param selectionArgs (Optional) Selection arguments used in the query. 137 | * @return The value of the _data column, which is typically a file path. 138 | */ 139 | private static String getDataColumn(Context context, Uri uri, String selection, 140 | String[] selectionArgs) { 141 | 142 | Cursor cursor = null; 143 | final String[] projection = { 144 | MediaStore.MediaColumns.DATA, 145 | MediaStore.MediaColumns.DISPLAY_NAME, 146 | }; 147 | 148 | try { 149 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 150 | null); 151 | if (cursor != null && cursor.moveToFirst()) { 152 | // Fall back to writing to file if _data column does not exist 153 | final int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); 154 | String path = index > -1 ? cursor.getString(index) : null; 155 | if (path != null) { 156 | return cursor.getString(index); 157 | } else { 158 | final int indexDisplayName = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); 159 | String fileName = cursor.getString(indexDisplayName); 160 | File fileWritten = writeToFile(context, fileName, uri); 161 | return fileWritten.getAbsolutePath(); 162 | } 163 | } 164 | } finally { 165 | if (cursor != null) 166 | cursor.close(); 167 | } 168 | return null; 169 | } 170 | 171 | 172 | /** 173 | * @param uri The Uri to check. 174 | * @return Whether the Uri authority is ExternalStorageProvider. 175 | */ 176 | private static boolean isExternalStorageDocument(Uri uri) { 177 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 178 | } 179 | 180 | /** 181 | * @param uri The Uri to check. 182 | * @return Whether the Uri authority is DownloadsProvider. 183 | */ 184 | private static boolean isDownloadsDocument(Uri uri) { 185 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 186 | } 187 | 188 | /** 189 | * @param uri The Uri to check. 190 | * @return Whether the Uri authority is MediaProvider. 191 | */ 192 | private static boolean isMediaDocument(Uri uri) { 193 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 194 | } 195 | 196 | /** 197 | * @param uri The Uri to check. 198 | * @return Whether the Uri authority is Google Photos. 199 | */ 200 | private static boolean isGooglePhotosUri(Uri uri) { 201 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 202 | } 203 | 204 | @TargetApi(Build.VERSION_CODES.KITKAT) 205 | private static String getPathToNonPrimaryVolume(Context context, String tag) { 206 | File[] volumes = context.getExternalCacheDirs(); 207 | if (volumes != null) { 208 | for (File volume : volumes) { 209 | if (volume != null) { 210 | String path = volume.getAbsolutePath(); 211 | if (path != null) { 212 | int index = path.indexOf(tag); 213 | if (index != -1) { 214 | return path.substring(0, index) + tag; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | return null; 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /android/src/main/java/chat/rocket/ShareModule.java: -------------------------------------------------------------------------------- 1 | package chat.rocket; 2 | 3 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactMethod; 7 | import com.facebook.react.bridge.WritableMap; 8 | import com.facebook.react.bridge.WritableArray; 9 | import com.facebook.react.bridge.Arguments; 10 | 11 | import android.app.Activity; 12 | import android.content.Intent; 13 | import android.net.Uri; 14 | 15 | import java.io.File; 16 | import java.util.ArrayList; 17 | 18 | public class ShareModule extends ReactContextBaseJavaModule { 19 | private File tempFolder; 20 | public static final String CACHE_DIR = "rcShare"; 21 | 22 | public ShareModule(ReactApplicationContext reactContext) { 23 | super(reactContext); 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return "ReactNativeShareExtension"; 29 | } 30 | 31 | @ReactMethod 32 | public void close() { 33 | getCurrentActivity().finish(); 34 | } 35 | 36 | @ReactMethod 37 | public void data(Promise promise) { 38 | promise.resolve(processIntent()); 39 | } 40 | 41 | public WritableArray processIntent() { 42 | WritableMap map = Arguments.createMap(); 43 | WritableArray items = Arguments.createArray(); 44 | 45 | String text = ""; 46 | String type = ""; 47 | String action = ""; 48 | 49 | Activity currentActivity = getCurrentActivity(); 50 | 51 | if (currentActivity != null) { 52 | tempFolder = new File(currentActivity.getCacheDir(), CACHE_DIR); 53 | 54 | Intent intent = currentActivity.getIntent(); 55 | action = intent.getAction(); 56 | type = intent.getType(); 57 | 58 | // Received some text 59 | if (Intent.ACTION_SEND.equals(action) && "text/plain".equals(type)) { 60 | text = intent.getStringExtra(Intent.EXTRA_TEXT); 61 | 62 | map.putString("value", text); 63 | map.putString("type", "text"); 64 | 65 | items.pushMap(map); 66 | 67 | // Received a single file 68 | } else if (Intent.ACTION_SEND.equals(action)) { 69 | Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); 70 | 71 | if (uri != null) { 72 | try { 73 | text = "file://" + RealPathUtil.getRealPathFromURI(currentActivity, uri); 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | } 77 | 78 | map.putString("value", text); 79 | map.putString("type", "media"); 80 | 81 | items.pushMap(map); 82 | } 83 | 84 | // Received multiple files 85 | } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { 86 | ArrayList uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 87 | 88 | for (Uri uri : uris) { 89 | String filePath = ""; 90 | try { 91 | filePath = RealPathUtil.getRealPathFromURI(currentActivity, uri); 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | } 95 | 96 | map = Arguments.createMap(); 97 | text = "file://" + filePath; 98 | 99 | map.putString("value", text); 100 | map.putString("type", "media"); 101 | 102 | items.pushMap(map); 103 | } 104 | } 105 | } 106 | 107 | return items; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /android/src/main/java/chat/rocket/SharePackage.java: -------------------------------------------------------------------------------- 1 | package chat.rocket; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.uimanager.ViewManager; 7 | import com.facebook.react.ReactPackage; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class SharePackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList(new ShareModule(reactContext)); 17 | } 18 | 19 | public List> createJSModules() { 20 | return Collections.emptyList(); 21 | } 22 | 23 | @Override 24 | public List createViewManagers(ReactApplicationContext reactContext) { 25 | return Collections.emptyList(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/NSExtensionActivationRule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/NSExtensionActivationRule.png -------------------------------------------------------------------------------- /assets/android-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/android-demo.gif -------------------------------------------------------------------------------- /assets/ios-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios-demo.gif -------------------------------------------------------------------------------- /assets/ios_step_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_01.png -------------------------------------------------------------------------------- /assets/ios_step_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_02.png -------------------------------------------------------------------------------- /assets/ios_step_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_03.png -------------------------------------------------------------------------------- /assets/ios_step_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_04.png -------------------------------------------------------------------------------- /assets/ios_step_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_05.png -------------------------------------------------------------------------------- /assets/ios_step_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_06.png -------------------------------------------------------------------------------- /assets/ios_step_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_07.png -------------------------------------------------------------------------------- /assets/ios_step_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_08.png -------------------------------------------------------------------------------- /assets/ios_step_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_09.png -------------------------------------------------------------------------------- /assets/ios_step_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_10.png -------------------------------------------------------------------------------- /assets/ios_step_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_11.png -------------------------------------------------------------------------------- /assets/ios_step_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_12.png -------------------------------------------------------------------------------- /assets/ios_step_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_13.png -------------------------------------------------------------------------------- /assets/ios_step_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_14.png -------------------------------------------------------------------------------- /assets/ios_step_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_15.png -------------------------------------------------------------------------------- /assets/ios_step_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_16.png -------------------------------------------------------------------------------- /assets/ios_step_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_17.png -------------------------------------------------------------------------------- /bin/react-native-xcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2015-present, Facebook, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed under the BSD-style license found in the 6 | # LICENSE file in the root directory of this source tree. An additional grant 7 | # of patent rights can be found in the PATENTS file in the same directory. 8 | 9 | # Bundle React Native app's code and image assets. 10 | # This script is supposed to be invoked as part of Xcode build process 11 | # and relies on environment variables (including PWD) set by Xcode 12 | 13 | DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH 14 | MAIN_BUNDLE="main.jsbundle" 15 | BUNDLE_FILE="$DEST/$MAIN_BUNDLE" 16 | TMP_PATH="/tmp" 17 | PLISTBUDDY='/usr/libexec/PlistBuddy' 18 | PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH 19 | 20 | [ -z "$SKIP_BUNDLING" ] && SKIP_BUNDLING=$($PLISTBUDDY -c "Print :BundleSkipped" "${PLIST}") 21 | [ -z "$CP_BUNDLING" ] && CP_BUNDLING=$($PLISTBUDDY -c "Print :BundleCopied" "${PLIST}") 22 | 23 | if [[ "$SKIP_BUNDLING" && $SKIP_BUNDLING == "true" ]]; then 24 | echo "SKIP_BUNDLING enabled; skipping." 25 | if [[ "$CP_BUNDLING" && $CP_BUNDLING == "true" ]]; then 26 | TMP_BUNDLE="$TMP_PATH/$MAIN_BUNDLE" 27 | echo "CP_BUNDLING enabled; copying $TMP_BUNDLE to $DEST/" 28 | if [ -f "$TMP_BUNDLE" ]; then 29 | cp "$TMP_PATH/$MAIN_BUNDLE"* "$DEST/" 30 | else 31 | echo "CP_BUNDLING $TMP_BUNDLE does not exist!" 32 | fi 33 | fi 34 | exit 0; 35 | fi 36 | 37 | [ -z "$IS_DEV" ] && IS_DEV=$($PLISTBUDDY -c "Print :BundleDev" "${PLIST}") 38 | [ -z "$FORCE_BUNDLING" ] && FORCE_BUNDLING=$($PLISTBUDDY -c "Print :BundleForced" "${PLIST}") 39 | 40 | if [ -z "$IS_DEV" ]; then 41 | case "$CONFIGURATION" in 42 | *Debug*) 43 | if [[ "$PLATFORM_NAME" == *simulator ]]; then 44 | if [[ "$FORCE_BUNDLING" && $FORCE_BUNDLING == "true" ]]; then 45 | echo "FORCE_BUNDLING enabled; continuing to bundle." 46 | else 47 | echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING env flag or BundleForced plist key to change this behavior." 48 | exit 0; 49 | fi 50 | else 51 | echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior." 52 | fi 53 | 54 | DEV=true 55 | ;; 56 | "") 57 | echo "$0 must be invoked by Xcode" 58 | exit 1 59 | ;; 60 | *) 61 | DEV=false 62 | ;; 63 | esac 64 | else 65 | if [[ "$PLATFORM_NAME" == *simulator ]]; then 66 | if [[ "$FORCE_BUNDLING" && $FORCE_BUNDLING == "true" ]]; then 67 | echo "FORCE_BUNDLING enabled; continuing to bundle." 68 | else 69 | echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING flag to change this behavior." 70 | exit 0; 71 | fi 72 | else 73 | echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior." 74 | fi 75 | DEV=$IS_DEV 76 | fi 77 | 78 | # Path to react-native folder inside node_modules 79 | # REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 80 | # Path to react-native folder inside src/native/utils/bin 81 | REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../node_modules/react-native" && pwd)" 82 | echo "REACT_NATIVE_DIR: $REACT_NATIVE_DIR" 83 | 84 | # Xcode project file for React Native apps is located in ios/ subfolder 85 | cd "${REACT_NATIVE_DIR}"/../.. 86 | 87 | # Define NVM_DIR and source the nvm.sh setup script 88 | [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" 89 | 90 | # Define default ENTRY_FILENAME 91 | [ -z "$ENTRY_FILENAME" ] && ENTRY_FILENAME=$($PLISTBUDDY -c "Print :BundleEntryFilename" "${PLIST}") 92 | [ -z "$ENTRY_FILENAME" ] && ENTRY_FILENAME="index.js" 93 | echo "ENTRY_FILENAME: $ENTRY_FILENAME" 94 | 95 | js_file_type=.js 96 | ios_file_type=.ios.js 97 | ios_file_name="${ENTRY_FILENAME/$js_file_type/$ios_file_type}" 98 | 99 | # Define entry file 100 | if [[ -s $ios_file_name ]]; then 101 | ENTRY_FILE=${1:-$ios_file_name} 102 | else 103 | ENTRY_FILE=${1:-$ENTRY_FILENAME} 104 | fi 105 | 106 | if [[ -s "$HOME/.nvm/nvm.sh" ]]; then 107 | . "$HOME/.nvm/nvm.sh" 108 | elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then 109 | . "$(brew --prefix nvm)/nvm.sh" 110 | fi 111 | 112 | # Set up the nodenv node version manager if present 113 | if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then 114 | eval "$("$HOME/.nodenv/bin/nodenv" init -)" 115 | fi 116 | 117 | [ -z "$NODE_BINARY" ] && export NODE_BINARY="node" 118 | 119 | [ -z "$CLI_PATH" ] && export CLI_PATH="$REACT_NATIVE_DIR/local-cli/cli.js" 120 | 121 | nodejs_not_found() 122 | { 123 | echo "error: Can't find '$NODE_BINARY' binary to build React Native bundle" >&2 124 | echo "If you have non-standard nodejs installation, select your project in Xcode," >&2 125 | echo "find 'Build Phases' - 'Bundle React Native code and images'" >&2 126 | echo "and change NODE_BINARY to absolute path to your node executable" >&2 127 | echo "(you can find it by invoking 'which node' in the terminal)" >&2 128 | exit 2 129 | } 130 | 131 | type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found 132 | 133 | # Print commands before executing them (useful for troubleshooting) 134 | set -x 135 | # DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH 136 | 137 | if [[ "$CONFIGURATION" = "Debug" && ! "$PLATFORM_NAME" == *simulator ]]; then 138 | BUNDLE_SERVER=$($PLISTBUDDY -c "Print :BundleServer" "${PLIST}") 139 | echo "BUNDLE_SERVER: ${BUNDLE_SERVER}" 140 | if [ -z "$BUNDLE_SERVER" ]; then 141 | IP=$(ipconfig getifaddr en0) 142 | if [ -z "$IP" ]; then 143 | IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | cut -d\ -f2 | awk 'NR==1{print $1}') 144 | fi 145 | else 146 | IP=$BUNDLE_SERVER 147 | fi 148 | 149 | if [ -z ${DISABLE_XIP+x} ]; then 150 | IP="$IP.xip.io" 151 | fi 152 | 153 | $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST" 154 | $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST" 155 | echo "$IP" > "$DEST/ip.txt" 156 | fi 157 | 158 | $NODE_BINARY "$CLI_PATH" bundle \ 159 | --entry-file "$ENTRY_FILE" \ 160 | --platform ios \ 161 | --dev $DEV \ 162 | --reset-cache \ 163 | --bundle-output "$BUNDLE_FILE" \ 164 | --assets-dest "$DEST" 165 | 166 | if [[ $DEV != true && ! -f "$BUNDLE_FILE" ]]; then 167 | echo "error: File $BUNDLE_FILE does not exist. This must be a bug with" >&2 168 | echo "React Native, please report it here: https://github.com/facebook/react-native/issues" 169 | exit 2 170 | else 171 | cp "$BUNDLE_FILE"* $TMP_PATH 172 | if [[ $DEV == "true" ]]; then 173 | if nc -w 5 -z localhost 8081 ; then 174 | if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running"; then 175 | echo "Port 8081 already in use, packager is either not running or not running correctly" 176 | exit 0 177 | fi 178 | else 179 | open "$REACT_NATIVE_DIR/scripts/launchPackager.command" || echo "Can't start packager automatically" 180 | fi 181 | fi 182 | fi 183 | -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "React/RCTBridgeModule.h" 3 | 4 | @interface ReactNativeShareExtension : UIViewController 5 | - (UIView*) shareView; 6 | @end 7 | -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #if __has_include() 5 | #import 6 | #endif 7 | 8 | #import "ReactNativeShareExtension.h" 9 | 10 | NSExtensionContext* extensionContext; 11 | 12 | @implementation ReactNativeShareExtension 13 | 14 | - (UIView*) shareView 15 | { 16 | return nil; 17 | } 18 | 19 | - (BOOL)isModalInPresentation { 20 | return true; 21 | } 22 | 23 | RCT_EXPORT_MODULE(); 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | extensionContext = self.extensionContext; 30 | 31 | UIView *rootView = [self shareView]; 32 | if (rootView.backgroundColor == nil) { 33 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:0.1]; 34 | } 35 | 36 | #if __has_include() 37 | [RCTUtilsUIOverride setPresentedViewController:self]; 38 | #endif 39 | 40 | self.view = rootView; 41 | } 42 | 43 | RCT_EXPORT_METHOD(openURL:(NSString *)url) 44 | { 45 | UIApplication *application = [UIApplication sharedApplication]; 46 | NSURL *openUrl = [NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 47 | if (@available(iOS 10.0, *)) { 48 | [application openURL:openUrl options:@{} completionHandler: nil]; 49 | } 50 | } 51 | 52 | RCT_EXPORT_METHOD(close) 53 | { 54 | [extensionContext completeRequestReturningItems:nil 55 | completionHandler:nil]; 56 | exit(0); 57 | } 58 | 59 | RCT_REMAP_METHOD(data, resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 60 | { 61 | [self extractDataFromContext:extensionContext withCallback:^(NSArray* items, NSException* err) { 62 | resolve(items); 63 | }]; 64 | } 65 | 66 | - (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(NSArray *items, NSException *exception))callback 67 | { 68 | __block NSMutableArray *data = [NSMutableArray new]; 69 | 70 | NSExtensionItem *item = [context.inputItems firstObject]; 71 | NSArray *attachments = item.attachments; 72 | __block NSUInteger index = 0; 73 | 74 | [attachments enumerateObjectsUsingBlock:^(NSItemProvider *provider, NSUInteger idx, BOOL *stop) 75 | { 76 | [provider.registeredTypeIdentifiers enumerateObjectsUsingBlock:^(NSString *identifier, NSUInteger idx, BOOL *stop) 77 | { 78 | [provider loadItemForTypeIdentifier:identifier options:nil completionHandler:^(id item, NSError *error) 79 | { 80 | index += 1; 81 | 82 | NSString *string; 83 | NSString *type; 84 | 85 | // is an URL - Can be a path or Web URL 86 | if ([(NSObject *)item isKindOfClass:[NSURL class]]) { 87 | NSURL *url = (NSURL *) item; 88 | string = [url absoluteString]; 89 | type = ([[string pathExtension] isEqualToString:@""]) || [url.scheme containsString:@"http"] ? @"text" : @"media"; 90 | 91 | [data addObject:@{ @"value": string, @"type": type }]; 92 | 93 | // is a String 94 | } else if ([(NSObject *)item isKindOfClass:[NSString class]]) { 95 | string = (NSString *)item; 96 | type = @"text"; 97 | 98 | [data addObject:@{ @"value": string, @"type": type }]; 99 | 100 | // is an Image 101 | } else if ([(NSObject *)item isKindOfClass:[UIImage class]]) { 102 | UIImage *sharedImage = (UIImage *)item; 103 | NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"image.png"]; 104 | [UIImagePNGRepresentation(sharedImage) writeToFile:path atomically:YES]; 105 | string = [NSString stringWithFormat:@"%@%@", @"file://", path]; 106 | type = @"media"; 107 | 108 | [data addObject:@{ @"value": string, @"type": type }]; 109 | } 110 | 111 | if (index == [attachments count]) { 112 | callback(data, nil); 113 | } 114 | }]; 115 | 116 | // We'll only use the first provider 117 | *stop = YES; 118 | }]; 119 | }]; 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 41B5DE3E1D0B50D300949BD5 /* ReactNativeShareExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B5DE3D1D0B50D300949BD5 /* ReactNativeShareExtension.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 41B5DE2E1D0B505800949BD5 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 41B5DE301D0B505800949BD5 /* libReactNativeShareExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeShareExtension.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 41B5DE3D1D0B50D300949BD5 /* ReactNativeShareExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeShareExtension.m; sourceTree = ""; }; 28 | 41B5DE3F1D0B50FA00949BD5 /* ReactNativeShareExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeShareExtension.h; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 41B5DE2D1D0B505800949BD5 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 41B5DE271D0B505800949BD5 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 41B5DE3C1D0B506B00949BD5 /* src */, 46 | 41B5DE311D0B505800949BD5 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 41B5DE311D0B505800949BD5 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 41B5DE301D0B505800949BD5 /* libReactNativeShareExtension.a */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 41B5DE3C1D0B506B00949BD5 /* src */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 41B5DE3F1D0B50FA00949BD5 /* ReactNativeShareExtension.h */, 62 | 41B5DE3D1D0B50D300949BD5 /* ReactNativeShareExtension.m */, 63 | ); 64 | name = src; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | 41B5DE2F1D0B505800949BD5 /* ReactNativeShareExtension */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = 41B5DE391D0B505800949BD5 /* Build configuration list for PBXNativeTarget "ReactNativeShareExtension" */; 73 | buildPhases = ( 74 | 41B5DE2C1D0B505800949BD5 /* Sources */, 75 | 41B5DE2D1D0B505800949BD5 /* Frameworks */, 76 | 41B5DE2E1D0B505800949BD5 /* CopyFiles */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = ReactNativeShareExtension; 83 | productName = ReactNativeShareExtension; 84 | productReference = 41B5DE301D0B505800949BD5 /* libReactNativeShareExtension.a */; 85 | productType = "com.apple.product-type.library.static"; 86 | }; 87 | /* End PBXNativeTarget section */ 88 | 89 | /* Begin PBXProject section */ 90 | 41B5DE281D0B505800949BD5 /* Project object */ = { 91 | isa = PBXProject; 92 | attributes = { 93 | LastUpgradeCheck = 0730; 94 | ORGANIZATIONNAME = "Ali Najafizadeh"; 95 | TargetAttributes = { 96 | 41B5DE2F1D0B505800949BD5 = { 97 | CreatedOnToolsVersion = 7.3.1; 98 | }; 99 | }; 100 | }; 101 | buildConfigurationList = 41B5DE2B1D0B505800949BD5 /* Build configuration list for PBXProject "ReactNativeShareExtension" */; 102 | compatibilityVersion = "Xcode 3.2"; 103 | developmentRegion = English; 104 | hasScannedForEncodings = 0; 105 | knownRegions = ( 106 | en, 107 | ); 108 | mainGroup = 41B5DE271D0B505800949BD5; 109 | productRefGroup = 41B5DE311D0B505800949BD5 /* Products */; 110 | projectDirPath = ""; 111 | projectRoot = ""; 112 | targets = ( 113 | 41B5DE2F1D0B505800949BD5 /* ReactNativeShareExtension */, 114 | ); 115 | }; 116 | /* End PBXProject section */ 117 | 118 | /* Begin PBXSourcesBuildPhase section */ 119 | 41B5DE2C1D0B505800949BD5 /* Sources */ = { 120 | isa = PBXSourcesBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | 41B5DE3E1D0B50D300949BD5 /* ReactNativeShareExtension.m in Sources */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | /* End PBXSourcesBuildPhase section */ 128 | 129 | /* Begin XCBuildConfiguration section */ 130 | 41B5DE371D0B505800949BD5 /* Debug */ = { 131 | isa = XCBuildConfiguration; 132 | buildSettings = { 133 | ALWAYS_SEARCH_USER_PATHS = NO; 134 | CLANG_ANALYZER_NONNULL = YES; 135 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 136 | CLANG_CXX_LIBRARY = "libc++"; 137 | CLANG_ENABLE_MODULES = YES; 138 | CLANG_ENABLE_OBJC_ARC = YES; 139 | CLANG_WARN_BOOL_CONVERSION = YES; 140 | CLANG_WARN_CONSTANT_CONVERSION = YES; 141 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 142 | CLANG_WARN_EMPTY_BODY = YES; 143 | CLANG_WARN_ENUM_CONVERSION = YES; 144 | CLANG_WARN_INT_CONVERSION = YES; 145 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 146 | CLANG_WARN_UNREACHABLE_CODE = YES; 147 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 148 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 149 | COPY_PHASE_STRIP = NO; 150 | DEBUG_INFORMATION_FORMAT = dwarf; 151 | ENABLE_STRICT_OBJC_MSGSEND = YES; 152 | ENABLE_TESTABILITY = YES; 153 | GCC_C_LANGUAGE_STANDARD = gnu99; 154 | GCC_DYNAMIC_NO_PIC = NO; 155 | GCC_NO_COMMON_BLOCKS = YES; 156 | GCC_OPTIMIZATION_LEVEL = 0; 157 | GCC_PREPROCESSOR_DEFINITIONS = ( 158 | "DEBUG=1", 159 | "$(inherited)", 160 | ); 161 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 162 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 163 | GCC_WARN_UNDECLARED_SELECTOR = YES; 164 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 165 | GCC_WARN_UNUSED_FUNCTION = YES; 166 | GCC_WARN_UNUSED_VARIABLE = YES; 167 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 168 | MTL_ENABLE_DEBUG_INFO = YES; 169 | ONLY_ACTIVE_ARCH = YES; 170 | SDKROOT = iphoneos; 171 | }; 172 | name = Debug; 173 | }; 174 | 41B5DE381D0B505800949BD5 /* Release */ = { 175 | isa = XCBuildConfiguration; 176 | buildSettings = { 177 | ALWAYS_SEARCH_USER_PATHS = NO; 178 | CLANG_ANALYZER_NONNULL = YES; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 180 | CLANG_CXX_LIBRARY = "libc++"; 181 | CLANG_ENABLE_MODULES = YES; 182 | CLANG_ENABLE_OBJC_ARC = YES; 183 | CLANG_WARN_BOOL_CONVERSION = YES; 184 | CLANG_WARN_CONSTANT_CONVERSION = YES; 185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INT_CONVERSION = YES; 189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 193 | COPY_PHASE_STRIP = NO; 194 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 195 | ENABLE_NS_ASSERTIONS = NO; 196 | ENABLE_STRICT_OBJC_MSGSEND = YES; 197 | GCC_C_LANGUAGE_STANDARD = gnu99; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 200 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 201 | GCC_WARN_UNDECLARED_SELECTOR = YES; 202 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 203 | GCC_WARN_UNUSED_FUNCTION = YES; 204 | GCC_WARN_UNUSED_VARIABLE = YES; 205 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 206 | MTL_ENABLE_DEBUG_INFO = NO; 207 | SDKROOT = iphoneos; 208 | VALIDATE_PRODUCT = YES; 209 | }; 210 | name = Release; 211 | }; 212 | 41B5DE3A1D0B505800949BD5 /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | HEADER_SEARCH_PATHS = ( 216 | "$(inhereted)", 217 | "$(SRCROOT)/../../../node_module/React/**", 218 | "$(SRCROOT)/../../../node_modules/react-native/React/**", 219 | ); 220 | OTHER_LDFLAGS = ( 221 | "-ObjC", 222 | "-lc++", 223 | ); 224 | PRODUCT_NAME = "$(TARGET_NAME)"; 225 | SKIP_INSTALL = YES; 226 | }; 227 | name = Debug; 228 | }; 229 | 41B5DE3B1D0B505800949BD5 /* Release */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | HEADER_SEARCH_PATHS = ( 233 | "$(inhereted)", 234 | "$(SRCROOT)/../../../node_module/React/**", 235 | "$(SRCROOT)/../../../node_modules/react-native/React/**", 236 | ); 237 | OTHER_LDFLAGS = ( 238 | "-ObjC", 239 | "-lc++", 240 | ); 241 | PRODUCT_NAME = "$(TARGET_NAME)"; 242 | SKIP_INSTALL = YES; 243 | }; 244 | name = Release; 245 | }; 246 | /* End XCBuildConfiguration section */ 247 | 248 | /* Begin XCConfigurationList section */ 249 | 41B5DE2B1D0B505800949BD5 /* Build configuration list for PBXProject "ReactNativeShareExtension" */ = { 250 | isa = XCConfigurationList; 251 | buildConfigurations = ( 252 | 41B5DE371D0B505800949BD5 /* Debug */, 253 | 41B5DE381D0B505800949BD5 /* Release */, 254 | ); 255 | defaultConfigurationIsVisible = 0; 256 | defaultConfigurationName = Release; 257 | }; 258 | 41B5DE391D0B505800949BD5 /* Build configuration list for PBXNativeTarget "ReactNativeShareExtension" */ = { 259 | isa = XCConfigurationList; 260 | buildConfigurations = ( 261 | 41B5DE3A1D0B505800949BD5 /* Debug */, 262 | 41B5DE3B1D0B505800949BD5 /* Release */, 263 | ); 264 | defaultConfigurationIsVisible = 0; 265 | }; 266 | /* End XCConfigurationList section */ 267 | }; 268 | rootObject = 41B5DE281D0B505800949BD5 /* Project object */; 269 | } 270 | -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/ios/ReactNativeShareExtension.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/ReactNativeShareExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/ReactNativeShareExtension.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ReactNativeShareExtension.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 41B5DE2F1D0B505800949BD5 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native' 2 | 3 | export default { 4 | data: () => NativeModules.ReactNativeShareExtension.data(), 5 | close: () => NativeModules.ReactNativeShareExtension.close(), 6 | openURL: (url) => NativeModules.ReactNativeShareExtension.openURL(url) 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-extensions-share", 3 | "version": "2.4.1", 4 | "description": "Share-Extension using react-native for both ios and android", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/RocketChat/rn-extensions-share" 12 | }, 13 | "keywords": [ 14 | "react-component", 15 | "react-native", 16 | "share-extension" 17 | ], 18 | "author": { 19 | "name": "Djorkaeff Alexandre", 20 | "email": "djorkaeffalexandre@gmail.com", 21 | "url": "http://github.com/djorkaeffalexandre" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/RocketChat/rn-extensions-share" 26 | }, 27 | "homepage": "https://github.com/RocketChat/rn-extensions-share" 28 | } 29 | -------------------------------------------------------------------------------- /rn-extensions-share.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = package['name'] 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | s.platform = :ios, "8.0" 14 | 15 | s.source = { :git => "https://github.com/RocketChat/rn-extensions-share.git", :tag => "master" } 16 | s.source_files = "ios/*.{h,m}" 17 | 18 | s.dependency 'React' 19 | end --------------------------------------------------------------------------------