├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── npm-publish.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── app.plugin.js ├── examples ├── basic │ ├── .gitignore │ ├── App.tsx │ ├── README.md │ ├── ShareExtension.tsx │ ├── app.json │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── fonts │ │ │ └── Inter-Black.otf │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── index.share.js │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── webpack.config.js ├── with-file │ ├── .gitignore │ ├── README.md │ ├── ShareExtension.tsx │ ├── app.json │ ├── app │ │ ├── create.tsx │ │ └── index.tsx │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── fonts │ │ │ └── Inter-Black.otf │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── index.share.js │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── webpack.config.js ├── with-firebase │ ├── .gitignore │ ├── App.tsx │ ├── README.md │ ├── ShareExtension.tsx │ ├── app.json │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── fonts │ │ │ └── Inter-Black.otf │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── components │ │ └── AppleAuthLogin.tsx │ ├── eas.json │ ├── index.js │ ├── index.share.js │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── webpack.config.js ├── with-media │ ├── .gitignore │ ├── README.md │ ├── ShareExtension.tsx │ ├── app.json │ ├── app │ │ ├── create.tsx │ │ └── index.tsx │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── fonts │ │ │ └── Inter-Black.otf │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── index.share.js │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── webpack.config.js ├── with-mmkv │ ├── .gitignore │ ├── App.tsx │ ├── README.md │ ├── ShareExtension.tsx │ ├── app.json │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── fonts │ │ │ └── Inter-Black.otf │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── index.share.js │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── storage.ts │ ├── tsconfig.json │ └── webpack.config.js └── with-preprocessing │ ├── .gitignore │ ├── App.tsx │ ├── README.md │ ├── ShareExtension.tsx │ ├── app.json │ ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── fonts │ │ └── Inter-Black.otf │ ├── icon.png │ └── splash.png │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── index.share.js │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── preprocessing.js │ ├── tsconfig.json │ └── webpack.config.js ├── expo-module.config.json ├── ios ├── ExpoShareExtension.podspec └── ExpoShareExtensionModule.swift ├── metro.js ├── package-lock.json ├── package.json ├── plugin ├── src │ ├── index.ts │ ├── withAppEntitlements.ts │ ├── withAppInfoPlist.ts │ ├── withExpoConfig.ts │ ├── withPodfile.ts │ ├── withShareExtensionEntitlements.ts │ ├── withShareExtensionInfoPlist.ts │ ├── withShareExtensionTarget.ts │ └── xcode │ │ ├── addBuildPhases.ts │ │ ├── addPbxGroup.ts │ │ ├── addProductFile.ts │ │ ├── addTargetDependency.ts │ │ ├── addToPbxNativeTargetSection.ts │ │ ├── addToPbxProjectSection.ts │ │ └── addToXCConfigurationList.ts ├── swift │ └── ShareExtensionViewController.swift └── tsconfig.json ├── src ├── ExpoShareExtensionModule.ts └── index.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['universe/native', 'universe/web'], 4 | ignorePatterns: ['build'], 5 | }; 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [MaxAst] 2 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | 20 | publish-npm: 21 | needs: build 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version: 16 28 | registry-url: https://registry.npmjs.org/ 29 | - run: npm ci 30 | - run: npm publish 31 | env: 32 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # VSCode 6 | .vscode/ 7 | jsconfig.json 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IJ 30 | # 31 | .classpath 32 | .cxx 33 | .gradle 34 | .idea 35 | .project 36 | .settings 37 | local.properties 38 | android.iml 39 | android/app/libs 40 | android/keystores/debug.keystore 41 | 42 | # Cocoapods 43 | # 44 | example/ios/Pods 45 | 46 | # Ruby 47 | example/vendor/ 48 | 49 | # node.js 50 | # 51 | node_modules/ 52 | npm-debug.log 53 | yarn-debug.log 54 | yarn-error.log 55 | 56 | # Expo 57 | .expo/* 58 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude all top-level hidden directories by convention 2 | /.*/ 3 | 4 | __mocks__ 5 | __tests__ 6 | 7 | /babel.config.js 8 | /android/src/androidTest/ 9 | /android/src/test/ 10 | /android/build/ 11 | /examples/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Max 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 | # Expo Share Extension 2 | 3 | ![npm](https://img.shields.io/npm/v/expo-share-extension.svg) 4 | ![License](https://img.shields.io/npm/l/expo-share-extension.svg) 5 | ![Downloads](https://img.shields.io/npm/dm/expo-share-extension.svg) 6 | ![GitHub stars](https://img.shields.io/github/stars/MaxAst/expo-share-extension.svg) 7 | 8 | > **Note**: Support for the New Architecture is under active development. For the time being, you need to disable it in your `app.json`/`app.config.(j|t)s`: 9 | > 10 | > ```json 11 | > { 12 | > "expo": { 13 | > "newArchEnabled": false 14 | > } 15 | > } 16 | > ``` 17 | 18 | ## Overview 19 | 20 | Create an [iOS share extension](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html) with a custom view (similar to e.g. Pinterest). Supports Apple Sign-In, [React Native Firebase](https://rnfirebase.io/) (including shared auth session via access groups), custom background, custom height, and custom fonts. 21 | 22 | https://github.com/MaxAst/expo-share-extension/assets/13224092/e5a6fb3d-6c85-4571-99c8-4efe0f862266 23 | 24 | ## Compatibility 25 | 26 | | Expo | `expo-share-extension` | 27 | | ---------- | ---------------------- | 28 | | **SDK 52** | 2.0.0+ | 29 | | **SDK 51** | 1.5.3+ | 30 | | **SDK 50** | 1.0.0+ | 31 | 32 | ## Quick Start 33 | 34 | ### 1. Installation 35 | 36 | ```sh 37 | npx expo install expo-share-extension 38 | ``` 39 | 40 | ### 2. Basic Configuration 41 | 42 | 1. Update your `app.json` or `app.config.js`: 43 | 44 | ```json 45 | "expo": { 46 | ... 47 | "plugins": ["expo-share-extension"], 48 | ... 49 | } 50 | ``` 51 | 52 | 2. Ensure your `package.json` has the correct `main` entry: 53 | 54 | ```json 55 | { 56 | ... 57 | "main": "index.js", 58 | ... 59 | } 60 | ``` 61 | 62 | 3. Create the required entry points: 63 | 64 | `index.js` (main app): 65 | 66 | ```ts 67 | import { registerRootComponent } from "expo"; 68 | 69 | import App from "./App"; 70 | 71 | registerRootComponent(App); 72 | 73 | // or if you're using expo-router: 74 | // import "expo-router/entry"; 75 | ``` 76 | 77 | `index.share.js` (share extension): 78 | 79 | ```ts 80 | import { AppRegistry } from "react-native"; 81 | 82 | // could be any component you want to use as the root component of your share extension's bundle 83 | import ShareExtension from "./ShareExtension"; 84 | 85 | // IMPORTANT: the first argument to registerComponent, must be "shareExtension" 86 | AppRegistry.registerComponent("shareExtension", () => ShareExtension); 87 | ``` 88 | 89 | 4. Wrap your metro config with `withShareExtension` in metro.config.js (if you don't have one, run: `npx expo customize metro.config.js` first): 90 | 91 | ```js 92 | // Learn more https://docs.expo.io/guides/customizing-metro 93 | const { getDefaultConfig } = require("expo/metro-config"); 94 | const { withShareExtension } = require("expo-share-extension/metro"); 95 | 96 | module.exports = withShareExtension(getDefaultConfig(__dirname), { 97 | // [Web-only]: Enables CSS support in Metro. 98 | isCSSEnabled: true, 99 | }); 100 | ``` 101 | 102 | ## Accessing Shared Data 103 | 104 | The shared data is passed to the share extension's root component as an initial prop based on this type: 105 | 106 | ```ts 107 | export type InitialProps = { 108 | files?: string[]; 109 | images?: string[]; 110 | videos?: string[]; 111 | text?: string; 112 | url?: string; 113 | preprocessingResults?: unknown; 114 | }; 115 | ``` 116 | 117 | You can import `InitialProps` from `expo-share-extension` to use it as a type for your root component's props. 118 | 119 | ## Activation Rules 120 | 121 | The config plugin supports almost all [NSExtensionActivationRules](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/AppExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW10). It currently supports. 122 | 123 | - `NSExtensionActivationSupportsText`, which is triggered e.g. when sharing a WhatsApp message's contents or when selecting a text on a webpage and sharing it via the iOS tooltip menu. The result is passed as the `text` field in the initial props 124 | - `NSExtensionActivationSupportsWebURLWithMaxCount: 1`, which is triggered when using the share button in Safari. The result is passed as the `url` field in the initial props 125 | - `NSExtensionActivationSupportsWebPageWithMaxCount: 1`, which is triggered when using the share button in Safari. The result is passed as the `preprocessingResults` field in the initial props. When using this rule, you will no longer receive `url` as part of initial props, unless you extract it in your preprocessing JavaScript file. You can learn more about this in the [Preprocessing JavaScript](#preprocessing-javascript) section. 126 | - `NSExtensionActivationSupportsImageWithMaxCount: 1`, which is triggered when using the share button on an image. The result is passed as part of the `images` array in the initial props. 127 | - `NSExtensionActivationSupportsMovieWithMaxCount: 1`, which is triggered when using the share button on a video. The result is passed as part of the `videos` array in the initial props. 128 | - `NSExtensionActivationSupportsFileWithMaxCount: 1`, which is triggered when using the share button on a file. The result is passed as part of the `files` array in the initial props. 129 | 130 | You need to list the activation rules you want to use in your `app.json`/`app.config.(j|t)s` file like so: 131 | 132 | ```json 133 | [ 134 | "expo-share-extension", 135 | { 136 | "activationRules": [ 137 | { 138 | "type": "file", 139 | "max": 3 140 | }, 141 | { 142 | "type": "image", 143 | "max": 2 144 | }, 145 | { 146 | "type": "video", 147 | "max": 1 148 | }, 149 | { 150 | "type": "text" 151 | }, 152 | { 153 | "type": "url", 154 | "max": 1 155 | } 156 | ] 157 | } 158 | ] 159 | ``` 160 | 161 | If no values for `max` are provided, the default value is `1`. The `type` field can be one of the following: `file`, `image`, `video`, `text`, `url`. 162 | 163 | If you want to use the `image` and `video` types, you need to make sure to add this to your `app.json`: 164 | 165 | ```jsonc 166 | { 167 | // ... 168 | "ios": { 169 | // ... 170 | "privacyManifests": { 171 | "NSPrivacyAccessedAPITypes": [ 172 | { 173 | "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp", 174 | "NSPrivacyAccessedAPITypeReasons": ["C617.1"], 175 | }, 176 | // ... 177 | ], 178 | }, 179 | }, 180 | } 181 | ``` 182 | 183 | If you do not specify the `activationRules` option, `expo-share-extension` enables the `url` and `text` rules by default, for backwards compatibility. 184 | 185 | Contributions to support the remaining [NSExtensionActivationRules](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/AppExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW10) (`NSExtensionActivationSupportsAttachmentsWithMaxCount` and `NSExtensionActivationSupportsAttachmentsWithMinCount`) are welcome! 186 | 187 | ## Basic Usage 188 | 189 | Need a way to close the share extension? Use the `close` method from `expo-share-extension`: 190 | 191 | ```ts 192 | import { close } from "expo-share-extension" 193 | import { Button, Text, View } from "react-native"; 194 | 195 | // if ShareExtension is your root component, url is available as an initial prop 196 | export default function ShareExtension({ url }: { url: string }) { 197 | return ( 198 | 199 | {url} 200 |