diff --git a/README.md b/README.md
index df839be..8448ffd 100644
--- a/README.md
+++ b/README.md
@@ -148,13 +148,14 @@ export default const App = () => {
const { shareIntent } = useShareIntent();
```
-| attribute | description | example |
-| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `shareIntent.text` | raw text from text/weburl (ios) and text/\* (android) | "`some text`", "`http://example.com`", "`Hey, Click on my link : http://example.com/nickname`" |
-| `shareIntent.webUrl` | link extracted from raw text | `null`, "`http://example.com`", "`http://example.com/nickname`" |
-| `shareIntent.files` | image / movies / audio / files with name, path, mimetype, size (in octets) and image/video dimensions (width/height/duration) | `[{ path: "file:///local/path/filename", mimeType: "image/jpeg", fileName: "originalFilename.jpg", size: 2567402, width: 800, height: 600 }, { path: "file:///local/path/filename", mimeType: "video/mp4", fileName: "originalFilename.mp4", size: 2567402, width: 800, height: 600, duration: 20000 }]` |
-| `shareIntent.meta` | meta object which contains extra information about the share intent | `{ title: "My cool blog article" }` |
-| `shareIntent.meta.title` | optional title property sent by other app. Currently only filled on Android | `My cool blog article` |
+| attribute | description | example |
+| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `shareIntent.text` | raw text from text/weburl (ios) and text/\* (android) | "`some text`", "`http://example.com`", "`Hey, Click on my link : http://example.com/nickname`" |
+| `shareIntent.webUrl` | link extracted from raw text | `null`, "`http://example.com`", "`http://example.com/nickname`" |
+| `shareIntent.files` | image / movies / audio / files with name, path, mimetype, size (in octets) and image/video dimensions (width/height/duration) | `[{ path: "file:///local/path/filename", mimeType: "image/jpeg", fileName: "originalFilename.jpg", size: 2567402, width: 800, height: 600 }, { path: "file:///local/path/filename", mimeType: "video/mp4", fileName: "originalFilename.mp4", size: 2567402, width: 800, height: 600, duration: 20000 }]` |
+| `shareIntent.meta` | meta object which contains extra information about the share intent | `{ title: "My cool blog article", "og:image": "https://.../image.png" }` |
+| `shareIntent.meta.title` | optional title property sent by other app (available on Android and when `NSExtensionActivationSupportsWebPageWithMaxCount` is enabled on iOS) | `My cool blog article` |
+| `shareIntent.meta.xxx` | list all webpage metadata available in meta tags `` (iOS only, available with `NSExtensionActivationSupportsWebPageWithMaxCount`) | |
#### Customize Content Types in `app.json`
@@ -177,16 +178,17 @@ Simply choose content types you need :
],
```
-| Option | Values |
-| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Option | Values |
+| ----------------------------- | ------------------- |
| iosActivationRules | Allow **text** sharing with `"NSExtensionActivationSupportsText": true`
**Url** sharing with `"NSExtensionActivationSupportsWebURLWithMaxCount": 1` and `"NSExtensionActivationSupportsWebPageWithMaxCount": 1`
**Images** sharing with `"NSExtensionActivationSupportsImageWithMaxCount": 1`
**Videos** sharing with `"NSExtensionActivationSupportsMovieWithMaxCount": 1`
**Files and audio** sharing with `"NSExtensionActivationSupportsFileWithMaxCount": 1`
_default value_: `{ "NSExtensionActivationSupportsWebURLWithMaxCount": 1, "NSExtensionActivationSupportsWebPageWithMaxCount": 1 }"`
_More info in apple developper doc [here](https://developer.apple.com/documentation/bundleresources/information_property_list/nsextension/nsextensionattributes/nsextensionactivationrule)_
you can also provide a custom query (ex: `"iosActivationRules": "SUBQUERY (...)"`) |
-| iosShareExtensionName | override `CFBundleDisplayName` the extension `info.plist`, also used as extension name for xcode target (ex: `ExpoShareIntent Example Extension`, folder: `ExpoShareIntentExampleExtension`) |
-| iosAppGroupIdentifier | custom application group identifier for `com.apple.security.application-groups` (ex: `group.custom.exposhareintent.example`) cf [#94](https://github.com/achorein/expo-share-intent/issues/94) |
-| androidIntentFilters | **one file sharing** array of MIME types :`"text/*"` / `"image/*"` / `"video/*"` / `"*/*"`
_default value_: `["text/*"]` (text and url) |
-| androidMultiIntentFilters | **multiple files sharing** array of MIME types : `"image/*"` / `"video/*"` / `"audio/*`/ `"*/*"`
_default value_: `[]` |
-| androidMainActivityAttributes | _default value_: `{ "android:launchMode": "singleTask" }` |
-| disableAndroid | Disable the android share intent. Useful if you want to use a custom implementation. _default value_: `false` |
-| disableIOS | Disable the ios share extension. Useful if you want to use a custom implementation (ex: [iOS Custom View](#ios-custom-view-)). _default value_: `false` |
+| iosShareExtensionName | override `CFBundleDisplayName` the extension `info.plist`, also used as extension name for xcode target (ex: `ExpoShareIntent Example Extension`, folder: `ExpoShareIntentExampleExtension`) |
+| iosAppGroupIdentifier | custom application group identifier for `com.apple.security.application-groups` (ex: `group.custom.exposhareintent.example`) cf [#94](https://github.com/achorein/expo-share-intent/issues/94) |
+| androidIntentFilters | **one file sharing** array of MIME types :`"text/*"` / `"image/*"` / `"video/*"` / `"*/*"`
_default value_: `["text/*"]` (text and url) |
+| androidMultiIntentFilters | **multiple files sharing** array of MIME types : `"image/*"` / `"video/*"` / `"audio/*`/ `"*/*"`
_default value_: `[]` |
+| androidMainActivityAttributes | _default value_: `{ "android:launchMode": "singleTask" }` |
+| preprocessorInjectJS | Add javascript to webpage preprocessor before the share extension is called (cf [Accessing a Webpage](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW12)).
Example: preprocessorInjectJS: "metas['og\\:image'] = metas['og\\:image'] || document.querySelector('img#seo-image')?.getAttribute('src')"
|
+| disableAndroid | Disable the android share intent. Useful if you want to use a custom implementation. _default value_: `false` |
+| disableIOS | Disable the ios share extension. Useful if you want to use a custom implementation (ex: [iOS Custom View](#ios-custom-view-)). _default value_: `false` |
### Expo Router
diff --git a/example/basic/App.tsx b/example/basic/App.tsx
index 22a0acf..100af85 100644
--- a/example/basic/App.tsx
+++ b/example/basic/App.tsx
@@ -1,8 +1,39 @@
import { Button, Image, StyleSheet, Text, View } from "react-native";
-import { useShareIntent, ShareIntentFile } from "expo-share-intent";
+import {
+ useShareIntent,
+ ShareIntentFile,
+ ShareIntent,
+} from "expo-share-intent";
import { Fragment } from "react";
+const WebUrlComponent = ({ shareIntent }: { shareIntent: ShareIntent }) => {
+ return (
+
+
+
+
+ {shareIntent.meta?.title || ""}
+
+ {shareIntent.webUrl}
+
+
+ );
+};
+
export default function App() {
const { hasShareIntent, shareIntent, resetShareIntent, error } =
useShareIntent({
@@ -22,8 +53,8 @@ export default function App() {
{/* TEXT and URL */}
{!!shareIntent.text && {shareIntent.text}}
- {!!shareIntent.meta?.title && (
- {JSON.stringify(shareIntent.meta)}
+ {shareIntent?.type === "weburl" && (
+
)}
{/* FILES */}
@@ -69,6 +100,7 @@ const styles = StyleSheet.create({
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
+ paddingHorizontal: 10,
},
logo: {
width: 75,
@@ -79,7 +111,16 @@ const styles = StyleSheet.create({
width: 300,
height: 200,
resizeMode: "contain",
- // backgroundColor: "lightgray",
+ },
+ icon: {
+ width: 100,
+ height: 100,
+ resizeMode: "contain",
+ backgroundColor: "lightgray",
+ },
+ row: {
+ flexDirection: "row",
+ gap: 10,
},
gap: {
marginBottom: 20,
diff --git a/example/basic/app.json b/example/basic/app.json
index 93d4135..b27db56 100644
--- a/example/basic/app.json
+++ b/example/basic/app.json
@@ -26,11 +26,9 @@
"NSExtensionActivationSupportsFileWithMaxCount": 1
},
"iosShareExtensionName": "ExpoShareIntent Example Extension",
- "iosAppGroupIdentifier": "customgroup.expo.modules.exposhareintent.example",
"androidIntentFilters": ["text/*", "image/*", "video/*"],
"androidMultiIntentFilters": ["image/*"],
- "disableIOS": false,
- "disableAndroid": false
+ "preprocessorInjectJS": "metas['og\\:image'] = metas['og\\:image'] || document.querySelector('img#main-image')?.getAttribute('src') ;"
}
],
["expo-updates"]
diff --git a/example/basic/package.json b/example/basic/package.json
index 3ca58f9..7344d9c 100644
--- a/example/basic/package.json
+++ b/example/basic/package.json
@@ -13,6 +13,8 @@
"test:fix": "expo install --fix",
"doctor": "npx --yes expo-doctor@latest",
"lint": "eslint .",
+ "open:ios": "open -a \"Xcode\" ios",
+ "open:android": "open -a \"Android Studio\" android",
"postinstall": "patch-package"
},
"dependencies": {
diff --git a/example/expo-router/app/shareintent.tsx b/example/expo-router/app/shareintent.tsx
index 547abad..78c6fc9 100644
--- a/example/expo-router/app/shareintent.tsx
+++ b/example/expo-router/app/shareintent.tsx
@@ -1,7 +1,37 @@
import { Button, Image, StyleSheet, Text, View } from "react-native";
import { useRouter } from "expo-router";
-import { useShareIntentContext } from "expo-share-intent";
+import {
+ ShareIntent as ShareIntentType,
+ useShareIntentContext,
+} from "expo-share-intent";
+
+const WebUrlComponent = ({ shareIntent }: { shareIntent: ShareIntentType }) => {
+ return (
+
+
+
+
+ {shareIntent.meta?.title || ""}
+
+ {shareIntent.webUrl}
+
+
+ );
+};
export default function ShareIntent() {
const router = useRouter();
@@ -21,8 +51,8 @@ export default function ShareIntent() {
)}
{!!shareIntent.text && {shareIntent.text}}
- {!!shareIntent.meta?.title && (
- {JSON.stringify(shareIntent.meta)}
+ {shareIntent?.type === "weburl" && (
+
)}
{shareIntent?.files?.map((file) => (
;
}
+const WebUrlComponent = ({ shareIntent }: { shareIntent: ShareIntent }) => {
+ return (
+
+
+
+
+ {shareIntent.meta?.title || ""}
+
+ {shareIntent.webUrl}
+
+
+ );
+};
+
export default function ShareIntentScreen({ navigation }: Props) {
const { hasShareIntent, shareIntent, resetShareIntent, error } =
useShareIntentContext();
@@ -22,8 +49,8 @@ export default function ShareIntentScreen({ navigation }: Props) {
{hasShareIntent ? "SHARE INTENT FOUND !" : "NO SHARE INTENT DETECTED"}
{!!shareIntent.text && {shareIntent.text}}
- {!!shareIntent.meta?.title && (
- {JSON.stringify(shareIntent.meta)}
+ {shareIntent?.type === "weburl" && (
+
)}
{shareIntent?.files?.map((file) => (
[SharedMediaFile] {
+ private func decodeMedia(data: Data) -> [SharedMediaFile] {
let encodedData = try? JSONDecoder().decode([SharedMediaFile].self, from: data)
return encodedData!
}
+ private func decodeWebUrl(data: Data) -> [WebUrl] {
+ let encodedData = try? JSONDecoder().decode([WebUrl].self, from: data)
+ return encodedData!
+ }
private func toJson(data: [SharedMediaFile]?) -> String? {
if data == nil {
@@ -203,12 +220,31 @@ public class ExpoShareIntentModule: Module {
let json = String(data: encodedData!, encoding: .utf8)!
return json
}
+
+ private func toJson(data: [WebUrl]?) -> String? {
+ if data == nil {
+ return nil
+ }
+ let encodedData = try? JSONEncoder().encode(data)
+ let json = String(data: encodedData!, encoding: .utf8)!
+ return json
+ }
struct ShareIntentText: Codable {
let text: String
let type: String // text / weburl
}
+ class WebUrl: Codable {
+ var url: String
+ var meta: String
+
+ init(url: String, meta: String) {
+ self.url = url
+ self.meta = meta
+ }
+ }
+
class SharedMediaFile: Codable {
var path: String // can be image, video or url path
var thumbnail: String? // video thumbnail
diff --git a/plugin/src/ios/ShareExtensionViewController.swift b/plugin/src/ios/ShareExtensionViewController.swift
index f422e57..a8b001a 100644
--- a/plugin/src/ios/ShareExtensionViewController.swift
+++ b/plugin/src/ios/ShareExtensionViewController.swift
@@ -14,13 +14,15 @@ class ShareViewController: UIViewController {
let shareProtocol = ""
let sharedKey = "ShareKey"
var sharedMedia: [SharedMediaFile] = []
+ var sharedWebUrl: [WebUrl] = []
var sharedText: [String] = []
- let imageContentType = kUTTypeImage as String
- let videoContentType = kUTTypeMovie as String
- let textContentType = kUTTypeText as String
- let urlContentType = kUTTypeURL as String
- let fileURLType = kUTTypeFileURL as String
- let pdfContentType = kUTTypePDF as String
+ let imageContentType: String = UTType.image.identifier
+ let videoContentType: String = UTType.movie.identifier
+ let textContentType: String = UTType.text.identifier
+ let urlContentType: String = UTType.url.identifier
+ let propertyListType: String = UTType.propertyList.identifier
+ let fileURLType: String = UTType.fileURL.identifier
+ let pdfContentType: String = UTType.pdf.identifier
override func viewDidLoad() {
super.viewDidLoad()
@@ -45,6 +47,8 @@ class ShareViewController: UIViewController {
await handleFiles(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(pdfContentType) {
await handlePdf(content: content, attachment: attachment, index: index)
+ } else if attachment.hasItemConformingToTypeIdentifier(propertyListType) {
+ await handlePrepocessing(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
await handleUrl(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
@@ -87,11 +91,11 @@ class ShareViewController: UIViewController {
if let item = try! await attachment.loadItem(forTypeIdentifier: self.urlContentType) as? URL {
Task { @MainActor in
- self.sharedText.append(item.absoluteString)
+ self.sharedWebUrl.append(WebUrl(url: item.absoluteString, meta: ""))
// If this is the last item, save sharedText in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier)
- userDefaults?.set(self.sharedText, forKey: self.sharedKey)
+ userDefaults?.set(self.toData(data: self.sharedWebUrl), forKey: self.sharedKey)
userDefaults?.synchronize()
self.redirectToHostApp(type: .weburl)
}
@@ -105,6 +109,46 @@ class ShareViewController: UIViewController {
}
}
+ private func handlePrepocessing(content: NSExtensionItem, attachment: NSItemProvider, index: Int)
+ async
+ {
+ Task.detached {
+ if let item = try! await attachment.loadItem(
+ forTypeIdentifier: self.propertyListType, options: nil)
+ as? NSDictionary
+ {
+ Task { @MainActor in
+
+ if let results = item[NSExtensionJavaScriptPreprocessingResultsKey]
+ as? NSDictionary
+ {
+ NSLog(
+ "[DEBUG] NSExtensionJavaScriptPreprocessingResultsKey \(String(describing: results))"
+ )
+ self.sharedWebUrl.append(
+ WebUrl(url: results["baseURI"] as! String, meta: results["meta"] as! String))
+ // If this is the last item, save sharedText in userDefaults and redirect to host app
+ if index == (content.attachments?.count)! - 1 {
+ let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier)
+ userDefaults?.set(self.toData(data: self.sharedWebUrl), forKey: self.sharedKey)
+ userDefaults?.synchronize()
+ self.redirectToHostApp(type: .weburl)
+ }
+ } else {
+ NSLog("[ERROR] Cannot load preprocessing results !\(String(describing: content))")
+ self.dismissWithError(
+ message: "Cannot load preprocessing results \(String(describing: content))")
+ }
+
+ }
+ } else {
+ NSLog("[ERROR] Cannot load preprocessing content !\(String(describing: content))")
+ await self.dismissWithError(
+ message: "Cannot load preprocessing content \(String(describing: content))")
+ }
+ }
+ }
+
private func handleImages(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async
{
Task.detached {
@@ -443,6 +487,16 @@ class ShareViewController: UIViewController {
return path
}
+ class WebUrl: Codable {
+ var url: String
+ var meta: String
+
+ init(url: String, meta: String) {
+ self.url = url
+ self.meta = meta
+ }
+ }
+
class SharedMediaFile: Codable {
var path: String // can be image, video or url path
var thumbnail: String? // video thumbnail
@@ -476,9 +530,13 @@ class ShareViewController: UIViewController {
case file
}
- func toData(data: [SharedMediaFile]) -> Data {
+ func toData(data: [WebUrl]) -> Data? {
+ let encodedData = try? JSONEncoder().encode(data)
+ return encodedData
+ }
+ func toData(data: [SharedMediaFile]) -> Data? {
let encodedData = try? JSONEncoder().encode(data)
- return encodedData!
+ return encodedData
}
}
diff --git a/plugin/src/ios/constants.ts b/plugin/src/ios/constants.ts
index b9daeeb..f2dfa71 100644
--- a/plugin/src/ios/constants.ts
+++ b/plugin/src/ios/constants.ts
@@ -6,6 +6,8 @@ export const shareExtensionInfoFileName = `${shareExtensionName}-Info.plist`;
export const shareExtensionEntitlementsFileName = `${shareExtensionName}.entitlements`;
export const shareExtensionStoryBoardFileName = "MainInterface.storyboard";
export const shareExtensionViewControllerFileName = "ShareViewController.swift";
+export const shareExtensionPreprocessorFileName =
+ "ShareExtensionPreprocessor.js";
export const getShareExtensionName = (parameters?: Parameters) => {
if (!parameters?.iosShareExtensionName) return shareExtensionName;
diff --git a/plugin/src/ios/withIosShareExtensionXcodeTarget.ts b/plugin/src/ios/withIosShareExtensionXcodeTarget.ts
index b0ad9b8..7062ac1 100644
--- a/plugin/src/ios/withIosShareExtensionXcodeTarget.ts
+++ b/plugin/src/ios/withIosShareExtensionXcodeTarget.ts
@@ -5,6 +5,7 @@ import {
getShareExtensionName,
} from "./constants";
import {
+ getPreprocessorFilePath,
getPrivacyInfoFilePath,
getShareExtensionEntitlementsFilePath,
getShareExtensionInfoFilePath,
@@ -40,21 +41,6 @@ export const withShareExtensionXcodeTarget: ConfigPlugin = (
platformProjectRoot,
parameters,
);
- // ShareViewController.swift
- const viewControllerFilePath = getShareExtensionViewControllerPath(
- platformProjectRoot,
- parameters,
- );
- // MainInterface.storyboard
- const storyboardFilePath = getShareExtensionStoryboardFilePath(
- platformProjectRoot,
- parameters,
- );
- // PrivacyInfo.xcprivacy
- const privacyFilePath = getPrivacyInfoFilePath(
- platformProjectRoot,
- parameters,
- );
await writeShareExtensionFiles(
platformProjectRoot,
@@ -102,21 +88,28 @@ export const withShareExtensionXcodeTarget: ConfigPlugin = (
// Add source files to our PbxGroup and our newly created PBXSourcesBuildPhase (ShareViewController.swift)
pbxProject.addSourceFile(
- viewControllerFilePath,
+ getShareExtensionViewControllerPath(platformProjectRoot, parameters),
{ target: target.uuid },
pbxGroupKey,
);
// Add the resource file and include it into the target PbxResourcesBuildPhase and PbxGroup
- // (MainInterface.storyboard / PrivacyInfo.xcprivacy)
try {
+ // ShareExtensionPreprocessor.js
+ pbxProject.addResourceFile(
+ getPreprocessorFilePath(platformProjectRoot, parameters),
+ { target: target.uuid },
+ pbxGroupKey,
+ );
+ // MainInterface.storyboard
pbxProject.addResourceFile(
- storyboardFilePath,
+ getShareExtensionStoryboardFilePath(platformProjectRoot, parameters),
{ target: target.uuid },
pbxGroupKey,
);
+ // PrivacyInfo.xcprivacy
pbxProject.addResourceFile(
- privacyFilePath,
+ getPrivacyInfoFilePath(platformProjectRoot, parameters),
{ target: target.uuid },
pbxGroupKey,
);
diff --git a/plugin/src/ios/writeIosShareExtensionFiles.ts b/plugin/src/ios/writeIosShareExtensionFiles.ts
index 92b8da0..654a7f3 100644
--- a/plugin/src/ios/writeIosShareExtensionFiles.ts
+++ b/plugin/src/ios/writeIosShareExtensionFiles.ts
@@ -10,6 +10,7 @@ import {
shareExtensionInfoFileName,
shareExtensionStoryBoardFileName,
shareExtensionViewControllerFileName,
+ shareExtensionPreprocessorFileName,
} from "./constants";
import { Parameters } from "../types";
@@ -70,6 +71,14 @@ export async function writeShareExtensionFiles(
getAppGroup(appIdentifier, parameters),
);
await fs.promises.writeFile(viewControllerFilePath, viewControllerContent);
+
+ // ShareExtensionPreprocessor.js
+ const preprocessorFilePath = getPreprocessorFilePath(
+ platformProjectRoot,
+ parameters,
+ );
+ const preprocessorContent = getPreprocessorContent(parameters);
+ await fs.promises.writeFile(preprocessorFilePath, preprocessorContent);
}
//: [root]/ios/ShareExtension/ShareExtension.entitlements
@@ -134,6 +143,7 @@ export function getShareExtensionInfoContent(
NSExtensionActivationSupportsWebURLWithMaxCount: 1,
NSExtensionActivationSupportsWebPageWithMaxCount: 1,
},
+ NSExtensionJavaScriptPreprocessingFile: "ShareExtensionPreprocessor",
},
NSExtensionMainStoryboard: "MainInterface",
NSExtensionPointIdentifier: "com.apple.share-services",
@@ -220,6 +230,50 @@ export function getShareExtensionViewControllerPath(
);
}
+export function getPreprocessorFilePath(
+ platformProjectRoot: string,
+ parameters: Parameters,
+) {
+ return path.join(
+ platformProjectRoot,
+ getShareExtensionName(parameters),
+ shareExtensionPreprocessorFileName,
+ );
+}
+
+export function getPreprocessorContent(parameters: Parameters) {
+ const injection = parameters.preprocessorInjectJS || "";
+ return `class ShareExtensionPreprocessor {
+ run({ completionFunction }) {
+ // Extract meta tags and image sources from the document
+ const metas = {
+ title: document.title,
+ };
+
+ // Get all meta elements
+ const metaElements = document.querySelectorAll("meta");
+ for (const meta of metaElements) {
+ const name = meta.getAttribute("name") || meta.getAttribute("property");
+ const content = meta.getAttribute("content");
+
+ if (name && content) {
+ metas[name] = content;
+ }
+ }
+
+ ${injection}
+
+ // Call the completion function with the extracted data
+ completionFunction({
+ baseURI: document.baseURI,
+ meta: JSON.stringify(metas),
+ });
+ }
+}
+var ExtensionPreprocessingJS = new ShareExtensionPreprocessor();
+`;
+}
+
export function getShareExtensionViewControllerContent(
scheme: string,
groupIdentifier: string,
diff --git a/plugin/src/types.ts b/plugin/src/types.ts
index 445b3f5..9e329c4 100644
--- a/plugin/src/types.ts
+++ b/plugin/src/types.ts
@@ -9,6 +9,7 @@ export type Parameters = {
androidIntentFilters?: ("text/*" | "image/*" | "video/*" | "*/*")[];
androidMultiIntentFilters?: ("image/*" | "video/*" | "*/*")[];
disableExperimental?: boolean;
+ preprocessorInjectJS?: string;
disableAndroid?: boolean;
disableIOS?: boolean;
};
diff --git a/src/ExpoShareIntentModule.types.ts b/src/ExpoShareIntentModule.types.ts
index 7f7d15c..5f7dd07 100644
--- a/src/ExpoShareIntentModule.types.ts
+++ b/src/ExpoShareIntentModule.types.ts
@@ -40,7 +40,7 @@ export type ShareIntentOptions = {
onResetShareIntent?: () => void;
};
-export type ShareIntentMeta = {
+export type ShareIntentMeta = Record & {
title?: string;
};
@@ -48,7 +48,7 @@ export type ShareIntentMeta = {
* Base type for what shared content is common between both platforms.
*/
interface BaseShareIntent {
- meta?: ShareIntentMeta;
+ meta?: ShareIntentMeta | null;
text?: string | null;
}
@@ -74,6 +74,7 @@ export interface AndroidShareIntent extends BaseShareIntent {
*/
export interface IosShareIntent extends BaseShareIntent {
files?: IosShareIntentFile[];
+ weburls?: { url: string; meta: string }[];
type: "media" | "file" | "text" | "weburl";
}
diff --git a/src/index.ts b/src/index.ts
index 962b31d..c5b9b52 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,9 +5,9 @@ export type {
export { default as ShareIntentModule } from "./ExpoShareIntentModule";
-export { default as useShareIntent, parseShareIntent } from "./useShareIntent";
+export { default as useShareIntent } from "./useShareIntent";
-export { getScheme, getShareExtensionKey } from "./utils";
+export { getScheme, getShareExtensionKey, parseShareIntent } from "./utils";
export {
ShareIntentProvider,
diff --git a/src/useShareIntent.tsx b/src/useShareIntent.tsx
index bf21186..e8ab687 100644
--- a/src/useShareIntent.tsx
+++ b/src/useShareIntent.tsx
@@ -3,14 +3,8 @@ import { useEffect, useRef, useState } from "react";
import { AppState, Platform } from "react-native";
import ExpoShareIntentModule from "./ExpoShareIntentModule";
-import {
- AndroidShareIntent,
- IosShareIntent,
- ShareIntent,
- ShareIntentFile,
- ShareIntentOptions,
-} from "./ExpoShareIntentModule.types";
-import { getScheme, getShareExtensionKey } from "./utils";
+import { ShareIntent, ShareIntentOptions } from "./ExpoShareIntentModule.types";
+import { getScheme, getShareExtensionKey, parseShareIntent } from "./utils";
export const SHAREINTENT_DEFAULTVALUE: ShareIntent = {
files: null,
@@ -25,83 +19,8 @@ export const SHAREINTENT_OPTIONS_DEFAULT: ShareIntentOptions = {
disabled: Platform.OS === "web",
};
-// const IOS_SHARE_TYPE_MAPPING = {
-// 0: "media",
-// 1: "text",
-// 2: "weburl",
-// 3: "file",
-// };
-
-export const parseShareIntent = (
- value: string | AndroidShareIntent,
- options: ShareIntentOptions,
-): ShareIntent => {
- let result = SHAREINTENT_DEFAULTVALUE;
- if (!value) return result;
- let shareIntent;
- // ios native module send a raw string of the json, try to parse it
- if (typeof value === "string") {
- shareIntent = JSON.parse(value) as IosShareIntent; // iOS
- } else {
- shareIntent = value; // Android
- }
-
- if (shareIntent.text) {
- // Try to find the webURL in the SharedIntent text
- const webUrl =
- shareIntent.text.match(
- /[(http(s)?)://(www.)?-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/gi,
- )?.[0] || null;
-
- result = {
- ...SHAREINTENT_DEFAULTVALUE,
- text: shareIntent.text,
- webUrl,
- type: webUrl ? "weburl" : "text",
- meta: {
- title: shareIntent.meta?.title ?? undefined,
- },
- };
- } else {
- // Ensure we got a valid file. some array value are emply
- const files =
- shareIntent?.files?.filter((file: any) => file.path || file.contentUri) ||
- [];
- const isMedia = files.every(
- (file) =>
- file.mimeType.startsWith("image/") ||
- file.mimeType.startsWith("video/"),
- );
- result = {
- ...SHAREINTENT_DEFAULTVALUE,
- files: shareIntent?.files
- ? shareIntent.files.reduce((acc: ShareIntentFile[], file: any) => {
- if (!file.path && !file.contentUri) return acc;
- return [
- ...acc,
- {
- path:
- file.path ||
- (file.filePath ? `file://${file.filePath}` : null) ||
- file.contentUri ||
- null,
- mimeType: file.mimeType || null,
- fileName: file.fileName || null,
- width: file.width ? Number(file.width) : null,
- height: file.height ? Number(file.height) : null,
- size: file.fileSize ? Number(file.fileSize) : null,
- duration: file.duration ? Number(file.duration) : null,
- },
- ];
- }, [])
- : null,
- type: isMedia ? "media" : "file",
- };
- }
- options.debug &&
- console.debug("useShareIntent[parsed] ", JSON.stringify(result, null, 2));
- return result;
-};
+const isValueAvailable = (shareIntent: ShareIntent) =>
+ !!(shareIntent?.text || shareIntent?.webUrl || shareIntent?.files);
export default function useShareIntent(
options: ShareIntentOptions = SHAREINTENT_OPTIONS_DEFAULT,
@@ -120,7 +39,7 @@ export default function useShareIntent(
setError(null);
clearNativeModule &&
ExpoShareIntentModule?.clearShareIntent(getShareExtensionKey(options));
- if (shareIntent?.text || shareIntent?.files) {
+ if (isValueAvailable(shareIntent)) {
setSharedIntent(SHAREINTENT_DEFAULTVALUE);
options.onResetShareIntent?.();
}
@@ -221,7 +140,7 @@ export default function useShareIntent(
return {
isReady,
- hasShareIntent: !!(shareIntent?.text || shareIntent?.files),
+ hasShareIntent: isValueAvailable(shareIntent),
shareIntent,
resetShareIntent,
error,
diff --git a/src/utils.ts b/src/utils.ts
index 2948768..48c1db8 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,7 +1,14 @@
import Constants from "expo-constants";
import { createURL } from "expo-linking";
-import { ShareIntentOptions } from "./ExpoShareIntentModule.types";
+import {
+ AndroidShareIntent,
+ IosShareIntent,
+ ShareIntent,
+ ShareIntentFile,
+ ShareIntentOptions,
+} from "./ExpoShareIntentModule.types";
+import { SHAREINTENT_DEFAULTVALUE } from "./useShareIntent";
export const getScheme = (options?: ShareIntentOptions) => {
if (options?.scheme !== undefined) {
@@ -41,3 +48,102 @@ export const getShareExtensionKey = (options?: ShareIntentOptions) => {
const scheme = getScheme(options);
return `${scheme}ShareKey`;
};
+
+// const IOS_SHARE_TYPE_MAPPING = {
+// 0: "media",
+// 1: "text",
+// 2: "weburl",
+// 3: "file",
+// };
+
+export function parseJson(
+ value: string,
+ defaultValue: T | null = null,
+): T | null {
+ try {
+ return JSON.parse(value) as T;
+ } catch (e) {
+ console.debug(e);
+ return defaultValue;
+ }
+}
+
+export const parseShareIntent = (
+ value: string | AndroidShareIntent,
+ options: ShareIntentOptions,
+): ShareIntent => {
+ let result = SHAREINTENT_DEFAULTVALUE;
+ if (!value) return result;
+ let shareIntent: IosShareIntent | AndroidShareIntent | null;
+ // ios native module send a raw string of the json, try to parse it
+ if (typeof value === "string") {
+ shareIntent = parseJson(value); // iOS
+ } else {
+ shareIntent = value; // Android
+ }
+
+ if (shareIntent?.text) {
+ // Try to find the webURL in the SharedIntent text
+ const webUrl =
+ shareIntent.text.match(
+ /[(http(s)?)://(www.)?-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/gi,
+ )?.[0] || null;
+
+ result = {
+ ...SHAREINTENT_DEFAULTVALUE,
+ type: webUrl ? "weburl" : "text",
+ text: shareIntent.text,
+ webUrl,
+ meta: {
+ title: shareIntent.meta?.title ?? undefined,
+ },
+ };
+ } else if ((shareIntent as IosShareIntent)?.weburls?.length) {
+ const weburl = (shareIntent as IosShareIntent).weburls![0];
+ result = {
+ ...SHAREINTENT_DEFAULTVALUE,
+ type: "weburl",
+ text: weburl.url, // retrocompatibility
+ webUrl: weburl.url,
+ meta: parseJson>(weburl.meta, {}),
+ };
+ } else {
+ // Ensure we got a valid file. some array value are emply
+ const files =
+ shareIntent?.files?.filter((file: any) => file.path || file.contentUri) ||
+ [];
+ const isMedia = files.every(
+ (file) =>
+ file.mimeType.startsWith("image/") ||
+ file.mimeType.startsWith("video/"),
+ );
+ result = {
+ ...SHAREINTENT_DEFAULTVALUE,
+ files: shareIntent?.files
+ ? shareIntent.files.reduce((acc: ShareIntentFile[], file: any) => {
+ if (!file.path && !file.contentUri) return acc;
+ return [
+ ...acc,
+ {
+ path:
+ file.path ||
+ (file.filePath ? `file://${file.filePath}` : null) ||
+ file.contentUri ||
+ null,
+ mimeType: file.mimeType || null,
+ fileName: file.fileName || null,
+ width: file.width ? Number(file.width) : null,
+ height: file.height ? Number(file.height) : null,
+ size: file.fileSize ? Number(file.fileSize) : null,
+ duration: file.duration ? Number(file.duration) : null,
+ },
+ ];
+ }, [])
+ : null,
+ type: isMedia ? "media" : "file",
+ };
+ }
+ options.debug &&
+ console.debug("useShareIntent[parsed] ", JSON.stringify(result, null, 2));
+ return result;
+};