From 561b5608237a9a6cf95f09ad884ab1dba3dcef42 Mon Sep 17 00:00:00 2001 From: Ngoc Le Date: Tue, 18 Aug 2026 11:16:35 +0700 Subject: [PATCH] chore: rename package to react-native-share-content --- .github/NPM_PUBLISHING.md | 2 +- CHANGELOG.md | 12 ++++++ LICENSE | 2 +- README.md | 17 ++++---- .../sharecontent/ShareContentIntentParser.kt | 4 +- .../modules/sharecontent/ShareContentQueue.kt | 4 +- docs/PRD.md | 4 +- docs/README.md | 8 ++-- docs/docs/api-reference.mdx | 2 +- docs/docs/configuration.mdx | 2 +- docs/docs/fundamentals/delivery-lifecycle.mdx | 2 +- docs/docs/fundamentals/how-it-works.mdx | 2 +- docs/docs/getting-started.mdx | 10 ++--- docs/docs/platforms/ios.mdx | 2 +- docs/docusaurus.config.ts | 30 ++++++------- docs/package-lock.json | 4 +- docs/package.json | 2 +- docs/src/pages/index.tsx | 6 +-- docs/static/img/og-image.svg | 2 +- example/App.tsx | 6 +-- example/app.json | 8 ++-- example/metro.config.js | 2 +- example/package-lock.json | 19 ++++----- example/package.json | 4 +- example/tsconfig.json | 4 +- example/webpack.config.js | 2 +- ios/ExpoShareContent.podspec | 6 +-- ios/ExpoShareContentModule.swift | 2 +- package-lock.json | 8 ++-- package.json | 14 ++++--- plugin/src/android.ts | 16 ++++--- plugin/src/index.ts | 2 +- plugin/src/ios/artifacts.ts | 10 +++-- plugin/src/ios/index.ts | 42 +++++++++++-------- plugin/src/options.ts | 42 +++++++++++-------- 35 files changed, 172 insertions(+), 132 deletions(-) diff --git a/.github/NPM_PUBLISHING.md b/.github/NPM_PUBLISHING.md index 3946e61..668988f 100644 --- a/.github/NPM_PUBLISHING.md +++ b/.github/NPM_PUBLISHING.md @@ -16,7 +16,7 @@ The npm package connection is restricted to: ```text Provider: GitHub Actions -Repository: ngocdevv/expo-share-content +Repository: ngocdevv/react-native-share-content Workflow: publish-npm.yml Allowed action: npm publish ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index a2ba70d..8ea9267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,3 +9,15 @@ ### 🐛 Bug fixes ### 💡 Others + +## 0.2.0 + +### 🛠 Breaking changes + +- Rename the npm package from `expo-share-content` to `react-native-share-content`. +- Update the Expo config plugin specifier and all installation/import examples to the new package name. + +### 💡 Others + +- Rename the GitHub repository and GitHub Pages project path to `react-native-share-content`. +- Keep the JavaScript API, native module names, Android package namespace, and legacy generated-code marker unchanged for upgrade compatibility. diff --git a/LICENSE b/LICENSE index 022c543..101e68e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 expo-share-content contributors +Copyright (c) 2026 react-native-share-content contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index fcc41bb..1008f90 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# expo-share-content +# react-native-share-content Receive text, URLs, images, videos, audio, and files shared **into** an Expo app. -`expo-share-content` is an Expo Module alternative to bridge-based packages such as `react-native-share-menu`. It supports cold starts, warm starts, multiple attachments, a durable pending queue, typed events, and Expo config plugins for Android share intents and an iOS Share Extension. +`react-native-share-content` is an Expo Module alternative to bridge-based packages such as `react-native-share-menu`. It supports cold starts, warm starts, multiple attachments, a durable pending queue, typed events, and Expo config plugins for Android share intents and an iOS Share Extension. + +> [!NOTE] +> This package was previously published as `expo-share-content`. Existing users should remove the old dependency, install `react-native-share-content`, update imports and the Expo config plugin entry, then run `npx expo prebuild --clean`. The JavaScript API and native module identifiers are unchanged. ## Demo | Android | iOS | | :---: | :---: | -| ![Receive shared content on Android](https://raw.githubusercontent.com/ngocdevv/expo-share-content/main/docs/static/img/demo-android.gif) | ![Receive shared content on iOS](https://raw.githubusercontent.com/ngocdevv/expo-share-content/main/docs/static/img/demo-ios.gif) | +| ![Receive shared content on Android](https://raw.githubusercontent.com/ngocdevv/react-native-share-content/main/docs/static/img/demo-android.gif) | ![Receive shared content on iOS](https://raw.githubusercontent.com/ngocdevv/react-native-share-content/main/docs/static/img/demo-ios.gif) | > [!IMPORTANT] > This package contains native code and does **not** work in Expo Go. Use a development build or production build after running prebuild. @@ -33,7 +36,7 @@ Delivery is **at least once**. A cold-start query and a live event can refer to ## Installation ```sh -npx expo install expo-share-content +npx expo install react-native-share-content ``` Add platform identifiers and the config plugin: @@ -50,7 +53,7 @@ Add platform identifiers and the config plugin: }, "plugins": [ [ - "expo-share-content", + "react-native-share-content", { "iosShareExtensionName": "ShareExtension" } @@ -93,7 +96,7 @@ Register the listener early, then inspect pending shares. Keep processing idempo ```tsx import ExpoShareContent, { type SharePayload, -} from 'expo-share-content'; +} from 'react-native-share-content'; import { useEffect } from 'react'; export function ShareReceiver() { @@ -258,7 +261,7 @@ Example with narrower Android filters and explicit Apple identifiers: ```json [ - "expo-share-content", + "react-native-share-content", { "androidIntentFilters": ["text/plain", "image/*"], "androidMultiIntentFilters": ["image/*"], diff --git a/android/src/main/java/expo/modules/sharecontent/ShareContentIntentParser.kt b/android/src/main/java/expo/modules/sharecontent/ShareContentIntentParser.kt index 9928793..606fd75 100644 --- a/android/src/main/java/expo/modules/sharecontent/ShareContentIntentParser.kt +++ b/android/src/main/java/expo/modules/sharecontent/ShareContentIntentParser.kt @@ -79,7 +79,7 @@ internal class ShareContentIntentParser( addItem(items, fileItem(stream, intent.type, shareId, intent)) } } catch (error: Throwable) { - File(context.noBackupFilesDir, "expo-share-content/$shareId").deleteRecursively() + File(context.noBackupFilesDir, "react-native-share-content/$shareId").deleteRecursively() throw error } @@ -234,7 +234,7 @@ internal class ShareContentIntentParser( throw IllegalArgumentException("Shared files exceed the $maxTotalSize-byte aggregate limit") } - val directory = File(context.noBackupFilesDir, "expo-share-content/$shareId") + val directory = File(context.noBackupFilesDir, "react-native-share-content/$shareId") check(directory.mkdirs() || directory.isDirectory) { "Cannot create share cache directory" } val safeName = File(proposedName).name diff --git a/android/src/main/java/expo/modules/sharecontent/ShareContentQueue.kt b/android/src/main/java/expo/modules/sharecontent/ShareContentQueue.kt index 966330e..12bf42c 100644 --- a/android/src/main/java/expo/modules/sharecontent/ShareContentQueue.kt +++ b/android/src/main/java/expo/modules/sharecontent/ShareContentQueue.kt @@ -143,7 +143,7 @@ internal object ShareContentQueue { require(id.isNotBlank() && id != "." && id != ".." && File(id).name == id) { "Invalid share receipt ID: $id" } - id to File(context.noBackupFilesDir, "expo-share-content/$id") + id to File(context.noBackupFilesDir, "react-native-share-content/$id") } for ((id, directory) in directories) { check(!directory.exists() || directory.deleteRecursively()) { @@ -169,7 +169,7 @@ internal object ShareContentQueue { } private fun storageRoot(context: Context): File = - File(context.noBackupFilesDir, "expo-share-content") + File(context.noBackupFilesDir, "react-native-share-content") private fun atomicQueueFile(context: Context): AtomicFile { val root = storageRoot(context) diff --git a/docs/PRD.md b/docs/PRD.md index f1b33b5..bac36c6 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1,10 +1,10 @@ -# Product requirements: expo-share-content +# Product requirements: react-native-share-content ## 1. Problem Expo applications need a reliable way to receive content from the native share sheet. Existing React Native packages commonly depend on the legacy bridge, manual AndroidManifest edits, and manually maintained iOS Share Extension targets. Event-only APIs also lose data when JavaScript is not ready during a cold start. -`expo-share-content` provides the same core capability using Expo Modules and an Expo config plugin. +`react-native-share-content` provides the same core capability using Expo Modules and an Expo config plugin. ## 2. Goals diff --git a/docs/README.md b/docs/README.md index 2d0ddbe..bae902e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# expo-share-content documentation +# react-native-share-content documentation -This directory contains the Docusaurus website for `expo-share-content`. +This directory contains the Docusaurus website for `react-native-share-content`. ## Local development @@ -10,7 +10,7 @@ npm install npm start ``` -The development server opens at `http://localhost:3000/expo-share-content/`. +The development server opens at `http://localhost:3000/react-native-share-content/`. ## Production build @@ -21,7 +21,7 @@ npm run serve ``` The static output is written to `docs/build/` and is configured for GitHub Pages at -`https://ngocdevv.github.io/expo-share-content/`. +`https://ngocdevv.github.io/react-native-share-content/`. ## Deployment diff --git a/docs/docs/api-reference.mdx b/docs/docs/api-reference.mdx index cda2835..b2f91c6 100644 --- a/docs/docs/api-reference.mdx +++ b/docs/docs/api-reference.mdx @@ -12,7 +12,7 @@ All share methods are available on the default export and as named exports. `ded import ExpoShareContent, { getPendingSharesAsync, type SharePayload, -} from 'expo-share-content'; +} from 'react-native-share-content'; ``` ## `getPendingSharesAsync` diff --git a/docs/docs/configuration.mdx b/docs/docs/configuration.mdx index 8ec4db2..a1d6d48 100644 --- a/docs/docs/configuration.mdx +++ b/docs/docs/configuration.mdx @@ -20,7 +20,7 @@ Pass options as the second item in the Expo config-plugin tuple. }, "plugins": [ [ - "expo-share-content", + "react-native-share-content", { "androidIntentFilters": ["text/plain", "image/*"], "androidMultiIntentFilters": ["image/*"], diff --git a/docs/docs/fundamentals/delivery-lifecycle.mdx b/docs/docs/fundamentals/delivery-lifecycle.mdx index c9007f3..6a1b961 100644 --- a/docs/docs/fundamentals/delivery-lifecycle.mdx +++ b/docs/docs/fundamentals/delivery-lifecycle.mdx @@ -70,7 +70,7 @@ await ExpoShareContent.releaseSharedFilesAsync([payload.id]); Use the named helper when combining event and query results: ```ts -import {dedupeShares} from 'expo-share-content'; +import {dedupeShares} from 'react-native-share-content'; const unique = dedupeShares([...pending, ...recentEvents]); ``` diff --git a/docs/docs/fundamentals/how-it-works.mdx b/docs/docs/fundamentals/how-it-works.mdx index f3063f6..ad43010 100644 --- a/docs/docs/fundamentals/how-it-works.mdx +++ b/docs/docs/fundamentals/how-it-works.mdx @@ -6,7 +6,7 @@ description: Understand the native intake, durable queue, and JavaScript process # How it works -Native share flows begin outside your React Native runtime. `expo-share-content` separates intake, persistence, and application processing so JavaScript startup timing does not decide whether content is delivered. +Native share flows begin outside your React Native runtime. `react-native-share-content` separates intake, persistence, and application processing so JavaScript startup timing does not decide whether content is delivered. ## The three layers diff --git a/docs/docs/getting-started.mdx b/docs/docs/getting-started.mdx index 44c7347..f1353e7 100644 --- a/docs/docs/getting-started.mdx +++ b/docs/docs/getting-started.mdx @@ -1,7 +1,7 @@ --- sidebar_position: 1 title: Getting started -description: Install expo-share-content, configure the Expo plugin, and receive your first shared payload. +description: Install react-native-share-content, configure the Expo plugin, and receive your first shared payload. --- # Getting started @@ -9,7 +9,7 @@ description: Install expo-share-content, configure the Expo plugin, and receive Receive text, URLs, images, video, audio, and files shared **into** an Expo app. :::info Native module required -`expo-share-content` does not work in Expo Go. Use a development build or a production build after running Expo prebuild. +`react-native-share-content` does not work in Expo Go. Use a development build or a production build after running Expo prebuild. ::: ## Requirements @@ -24,7 +24,7 @@ Receive text, URLs, images, video, audio, and files shared **into** an Expo app. ## Install ```bash -npx expo install expo-share-content +npx expo install react-native-share-content ``` ## Add the config plugin @@ -43,7 +43,7 @@ Add platform identifiers and the package to your Expo configuration: }, "plugins": [ [ - "expo-share-content", + "react-native-share-content", { "iosShareExtensionName": "ShareExtension", "iosOpenHostAppAfterShare": false @@ -67,7 +67,7 @@ Register listeners early, then query pending payloads. Keep processing idempoten ```tsx title="ShareReceiver.tsx" import ExpoShareContent, { type SharePayload, -} from 'expo-share-content'; +} from 'react-native-share-content'; import { useEffect } from 'react'; export function ShareReceiver() { diff --git a/docs/docs/platforms/ios.mdx b/docs/docs/platforms/ios.mdx index 6a78ec8..842cb1d 100644 --- a/docs/docs/platforms/ios.mdx +++ b/docs/docs/platforms/ios.mdx @@ -60,7 +60,7 @@ Set it to `true` only if you want a best-effort foreground attempt: "scheme": "myapp", "plugins": [ [ - "expo-share-content", + "react-native-share-content", { "iosOpenHostAppAfterShare": true } diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 3b2e411..b7c3705 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -3,13 +3,13 @@ import type {Options, ThemeConfig} from '@docusaurus/preset-classic'; import {themes as prismThemes} from 'prism-react-renderer'; const config: Config = { - title: 'Expo Share Content', + title: 'React Native Share Content', tagline: 'Receive shared content reliably in Expo apps', favicon: 'img/favicon.svg', url: 'https://ngocdevv.github.io', - baseUrl: '/expo-share-content/', + baseUrl: '/react-native-share-content/', organizationName: 'ngocdevv', - projectName: 'expo-share-content', + projectName: 'react-native-share-content', trailingSlash: false, onBrokenLinks: 'throw', markdown: { @@ -27,7 +27,7 @@ const config: Config = { breadcrumbs: true, showLastUpdateTime: false, editUrl: - 'https://github.com/ngocdevv/expo-share-content/edit/main/docs/', + 'https://github.com/ngocdevv/react-native-share-content/edit/main/docs/', }, blog: false, theme: { @@ -51,25 +51,25 @@ const config: Config = { respectPrefersColorScheme: false, }, navbar: { - title: 'Expo Share Content', + title: 'React Native Share Content', hideOnScroll: false, logo: { - alt: 'Expo Share Content logo', + alt: 'React Native Share Content logo', src: 'img/logo.svg', }, items: [ { - href: '/expo-share-content/?section=features', + href: '/react-native-share-content/?section=features', label: 'Features', position: 'left', }, { - href: '/expo-share-content/?section=showcase', + href: '/react-native-share-content/?section=showcase', label: 'Example', position: 'left', }, { - href: '/expo-share-content/?section=faq', + href: '/react-native-share-content/?section=faq', label: 'FAQ', position: 'left', }, @@ -80,12 +80,12 @@ const config: Config = { position: 'right', }, { - href: 'https://www.npmjs.com/package/expo-share-content', + href: 'https://www.npmjs.com/package/react-native-share-content', label: 'npm', position: 'right', }, { - href: 'https://github.com/ngocdevv/expo-share-content', + href: 'https://github.com/ngocdevv/react-native-share-content', label: 'GitHub', position: 'right', className: 'navbar-github-link', @@ -121,20 +121,20 @@ const config: Config = { items: [ { label: 'GitHub', - href: 'https://github.com/ngocdevv/expo-share-content', + href: 'https://github.com/ngocdevv/react-native-share-content', }, { label: 'npm', - href: 'https://www.npmjs.com/package/expo-share-content', + href: 'https://www.npmjs.com/package/react-native-share-content', }, { label: 'License', - href: 'https://github.com/ngocdevv/expo-share-content/blob/main/LICENSE', + href: 'https://github.com/ngocdevv/react-native-share-content/blob/main/LICENSE', }, ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} expo-share-content. MIT licensed.`, + copyright: `Copyright © ${new Date().getFullYear()} react-native-share-content. MIT licensed.`, }, prism: { theme: prismThemes.github, diff --git a/docs/package-lock.json b/docs/package-lock.json index 17bf959..e613277 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,11 +1,11 @@ { - "name": "expo-share-content-docs", + "name": "react-native-share-content-docs", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "expo-share-content-docs", + "name": "react-native-share-content-docs", "version": "0.1.0", "dependencies": { "@docusaurus/core": "3.10.2", diff --git a/docs/package.json b/docs/package.json index 8c19dd7..fd3902f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,5 +1,5 @@ { - "name": "expo-share-content-docs", + "name": "react-native-share-content-docs", "version": "0.1.0", "private": true, "scripts": { diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 920d629..e41a047 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -5,7 +5,7 @@ import Link from '@docusaurus/Link'; import clsx from 'clsx'; import styles from './index.module.css'; -const INSTALL_COMMAND = 'npx expo install expo-share-content'; +const INSTALL_COMMAND = 'npx expo install react-native-share-content'; const features = [ { @@ -203,7 +203,7 @@ function ShareFlow() { Privacy-safe Expo Share Content demo showing an image payload with preview, filename, size, and App Group file URI View on GitHub diff --git a/docs/static/img/og-image.svg b/docs/static/img/og-image.svg index b8c9e5b..afbf89e 100644 --- a/docs/static/img/og-image.svg +++ b/docs/static/img/og-image.svg @@ -1 +1 @@ -Expo Share ContentReceive shared content reliably.Cold starts included. +React Native Share ContentReceive shared content reliably.Cold starts included. diff --git a/example/App.tsx b/example/App.tsx index ada4699..eed417b 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,7 +1,7 @@ import ExpoShareContent, { type SharedContentItem, type SharePayload, -} from 'expo-share-content'; +} from 'react-native-share-content'; import { useCallback, useEffect, useState } from 'react'; import { Button, @@ -255,9 +255,9 @@ export default function App() { return ( - Expo Share Content + React Native Share Content - Open another app, choose Share, then select “Expo Share Content Example” on Android or + Open another app, choose Share, then select “React Native Share Content Example” on Android or “Share to Example” on iOS. Received text, links, images, and files show below with a readable preview (not only raw JSON). diff --git a/example/app.json b/example/app.json index b0decbd..8542d9b 100644 --- a/example/app.json +++ b/example/app.json @@ -1,15 +1,15 @@ { "expo": { - "name": "Expo Share Content Example", - "slug": "expo-share-content-example", + "name": "React Native Share Content Example", + "slug": "react-native-share-content-example", "version": "1.0.0", - "scheme": "expo-share-content-example", + "scheme": "react-native-share-content-example", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", "plugins": [ [ - "expo-share-content", + "react-native-share-content", { "androidIntentFilters": ["text/*", "image/*", "video/*", "audio/*", "application/*"], "androidMultiIntentFilters": ["image/*", "video/*", "audio/*", "application/*"], diff --git a/example/metro.config.js b/example/metro.config.js index 5e40ae6..22495ca 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -20,7 +20,7 @@ config.resolver.nodeModulesPaths = [ ]; config.resolver.extraNodeModules = { - 'expo-share-content-scaffold': '..', + 'react-native-share-content-scaffold': '..', }; config.watchFolders = [path.resolve(__dirname, '..')]; diff --git a/example/package-lock.json b/example/package-lock.json index 7069ca5..a5e3d3a 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -1,17 +1,17 @@ { - "name": "expo-share-content-example", + "name": "react-native-share-content-example", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "expo-share-content-example", + "name": "react-native-share-content-example", "version": "1.0.0", "dependencies": { "expo": "~57.0.11", - "expo-share-content": "file:..", "react": "19.2.3", - "react-native": "0.86.2" + "react-native": "0.86.2", + "react-native-share-content": "file:.." }, "devDependencies": { "@types/react": "~19.2.2", @@ -19,8 +19,7 @@ } }, "..": { - "name": "expo-share-content", - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "dependencies": { "@expo/config-plugins": "~57.0.7", @@ -2728,10 +2727,6 @@ "node": ">=20.16.0" } }, - "node_modules/expo-share-content": { - "resolved": "..", - "link": true - }, "node_modules/expo/node_modules/@expo/cli": { "version": "57.0.13", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.13.tgz", @@ -5044,6 +5039,10 @@ } } }, + "node_modules/react-native-share-content": { + "resolved": "..", + "link": true + }, "node_modules/react-native/node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", diff --git a/example/package.json b/example/package.json index 046b4c1..caf7ec1 100644 --- a/example/package.json +++ b/example/package.json @@ -1,10 +1,10 @@ { - "name": "expo-share-content-example", + "name": "react-native-share-content-example", "version": "1.0.0", "main": "index.ts", "dependencies": { "expo": "~57.0.11", - "expo-share-content": "file:..", + "react-native-share-content": "file:..", "react": "19.2.3", "react-native": "0.86.2" }, diff --git a/example/tsconfig.json b/example/tsconfig.json index ed1f665..b2b2374 100644 --- a/example/tsconfig.json +++ b/example/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "strict": true, "paths": { - "expo-share-content-scaffold": ["../src/index"], - "expo-share-content-scaffold/*": ["../src/*"] + "react-native-share-content-scaffold": ["../src/index"], + "react-native-share-content-scaffold/*": ["../src/*"] } } } diff --git a/example/webpack.config.js b/example/webpack.config.js index 2bdcf3b..521170d 100644 --- a/example/webpack.config.js +++ b/example/webpack.config.js @@ -6,7 +6,7 @@ module.exports = async (env, argv) => { { ...env, babel: { - dangerouslyAddModulePathsToTranspile: ['expo-share-content-scaffold'], + dangerouslyAddModulePathsToTranspile: ['react-native-share-content-scaffold'], }, }, argv diff --git a/ios/ExpoShareContent.podspec b/ios/ExpoShareContent.podspec index 10442e6..2347229 100644 --- a/ios/ExpoShareContent.podspec +++ b/ios/ExpoShareContent.podspec @@ -1,13 +1,13 @@ Pod::Spec.new do |s| s.name = 'ExpoShareContent' - s.version = '0.1.0' + s.version = '0.2.0' s.summary = 'Receive content shared to an Expo app.' s.description = 'An Expo Module for receiving text, URLs, images, videos, audio, and files from the system share sheet.' s.author = 'ngocdevv' - s.homepage = 'https://github.com/ngocdevv/expo-share-content' + s.homepage = 'https://github.com/ngocdevv/react-native-share-content' s.license = { :type => 'MIT', :file => '../LICENSE' } s.platforms = { :ios => '16.4' } - s.source = { :http => "https://registry.npmjs.org/expo-share-content/-/expo-share-content-#{s.version}.tgz" } + s.source = { :http => "https://registry.npmjs.org/react-native-share-content/-/react-native-share-content-#{s.version}.tgz" } s.static_framework = true s.dependency 'ExpoModulesCore' diff --git a/ios/ExpoShareContentModule.swift b/ios/ExpoShareContentModule.swift index 2207085..0da0203 100644 --- a/ios/ExpoShareContentModule.swift +++ b/ios/ExpoShareContentModule.swift @@ -95,7 +95,7 @@ public final class ExpoShareContentModule: Module { throw error( code: 1, message: - "ExpoShareContentAppGroup is missing. Add the expo-share-content config plugin and rebuild the native app." + "ExpoShareContentAppGroup is missing. Add the react-native-share-content config plugin and rebuild the native app." ) } return value diff --git a/package-lock.json b/package-lock.json index fe8ec65..97ae1a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "expo-share-content", - "version": "0.1.2", + "name": "react-native-share-content", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "expo-share-content", - "version": "0.1.2", + "name": "react-native-share-content", + "version": "0.2.0", "license": "MIT", "dependencies": { "@expo/config-plugins": "~57.0.7", diff --git a/package.json b/package.json index 6d976d8..35b2b27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "expo-share-content", - "version": "0.1.2", + "name": "@smoke/react-native-share-content", + "version": "0.2.0", "description": "Receive text, URLs, images, videos, audio, and files shared to an Expo app.", "type": "commonjs", "main": "build/index.js", @@ -58,11 +58,11 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/ngocdevv/expo-share-content.git" + "url": "git+https://github.com/ngocdevv/react-native-share-content.git" }, - "homepage": "https://ngocdevv.github.io/expo-share-content/", + "homepage": "https://ngocdevv.github.io/react-native-share-content/", "bugs": { - "url": "https://github.com/ngocdevv/expo-share-content/issues" + "url": "https://github.com/ngocdevv/react-native-share-content/issues" }, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >=25.0.0" @@ -100,5 +100,9 @@ "expo": "^57.0.0", "react": "*", "react-native": "*" + }, + "directories": { + "doc": "docs", + "example": "example" } } diff --git a/plugin/src/android.ts b/plugin/src/android.ts index c6037fe..afaf079 100644 --- a/plugin/src/android.ts +++ b/plugin/src/android.ts @@ -49,7 +49,7 @@ export function applyAndroidShareConfig( const application = androidManifest.manifest.application?.[0]; if (!application) { - throw new Error('[expo-share-content] Android manifest has no application node.'); + throw new Error('[react-native-share-content] Android manifest has no application node.'); } const metadataNames = new Set([ 'expo.modules.sharecontent.MAX_SHARED_ITEMS', @@ -114,7 +114,9 @@ export function applyAndroidMainActivity(contents: string, language: string): st /(\s*)setIntent\((\w+)\); \/\/ expo-share-content-intent\n\s*ShareContentIntentHolder\.offer\(\2\);/; const pattern = language === 'kt' ? kotlinLegacy : language === 'java' ? javaLegacy : null; if (!pattern) { - throw new Error(`[expo-share-content] Unsupported MainActivity language: ${language}`); + throw new Error( + `[react-native-share-content] Unsupported MainActivity language: ${language}` + ); } const migrated = contents.replace( pattern, @@ -124,7 +126,7 @@ export function applyAndroidMainActivity(contents: string, language: string): st ); if (migrated === contents) { throw new Error( - '[expo-share-content] Unable to update the existing MainActivity intent bridge. Run a clean prebuild.' + '[react-native-share-content] Unable to update the existing MainActivity intent bridge. Run a clean prebuild.' ); } return migrated; @@ -159,7 +161,8 @@ export function applyAndroidMainActivity(contents: string, language: string): st } `; const closingBrace = updated.lastIndexOf('}'); - if (closingBrace < 0) throw new Error('[expo-share-content] Invalid Kotlin MainActivity.'); + if (closingBrace < 0) + throw new Error('[react-native-share-content] Invalid Kotlin MainActivity.'); return `${updated.slice(0, closingBrace)}${method}${updated.slice(closingBrace)}`; } @@ -193,11 +196,12 @@ export function applyAndroidMainActivity(contents: string, language: string): st } `; const closingBrace = updated.lastIndexOf('}'); - if (closingBrace < 0) throw new Error('[expo-share-content] Invalid Java MainActivity.'); + if (closingBrace < 0) + throw new Error('[react-native-share-content] Invalid Java MainActivity.'); return `${updated.slice(0, closingBrace)}${method}${updated.slice(closingBrace)}`; } - throw new Error(`[expo-share-content] Unsupported MainActivity language: ${language}`); + throw new Error(`[react-native-share-content] Unsupported MainActivity language: ${language}`); } export const withAndroidShareContent: ConfigPlugin = (config, options) => { diff --git a/plugin/src/index.ts b/plugin/src/index.ts index 294c98e..398d5ef 100644 --- a/plugin/src/index.ts +++ b/plugin/src/index.ts @@ -18,4 +18,4 @@ const withExpoShareContent: ConfigPlugin = export { resolvePluginOptions } from './options'; export type { ExpoShareContentPluginOptions } from './options'; -export default createRunOncePlugin(withExpoShareContent, 'expo-share-content', '0.1.0'); +export default createRunOncePlugin(withExpoShareContent, 'react-native-share-content', '0.1.0'); diff --git a/plugin/src/ios/artifacts.ts b/plugin/src/ios/artifacts.ts index 87bfc9d..0ebf22b 100644 --- a/plugin/src/ios/artifacts.ts +++ b/plugin/src/ios/artifacts.ts @@ -20,13 +20,15 @@ export function resolveIosIdentifiers( options: ResolvedPluginOptions ): IosIdentifiers { if (!identity.bundleIdentifier) { - throw new Error('[expo-share-content] expo.ios.bundleIdentifier is required.'); + throw new Error('[react-native-share-content] expo.ios.bundleIdentifier is required.'); } const displayName = options.iosShareExtensionName; const targetName = displayName.replace(/[^a-zA-Z0-9]/g, ''); if (!targetName) { - throw new Error('[expo-share-content] iosShareExtensionName must contain letters or numbers.'); + throw new Error( + '[react-native-share-content] iosShareExtensionName must contain letters or numbers.' + ); } return { @@ -47,12 +49,12 @@ function resolveShareExtensionBundleIdentifier( const value = override?.trim() || `${hostBundleIdentifier}.share`; if (value === hostBundleIdentifier) { throw new Error( - '[expo-share-content] iosShareExtensionBundleIdentifier must differ from the host bundle identifier.' + '[react-native-share-content] iosShareExtensionBundleIdentifier must differ from the host bundle identifier.' ); } if (!value.startsWith(`${hostBundleIdentifier}.`)) { throw new Error( - `[expo-share-content] iosShareExtensionBundleIdentifier must be prefixed by the host bundle identifier (${hostBundleIdentifier}.).` + `[react-native-share-content] iosShareExtensionBundleIdentifier must be prefixed by the host bundle identifier (${hostBundleIdentifier}.).` ); } return value; diff --git a/plugin/src/ios/index.ts b/plugin/src/ios/index.ts index 645a93c..cf5879f 100644 --- a/plugin/src/ios/index.ts +++ b/plugin/src/ios/index.ts @@ -46,7 +46,7 @@ function registerHostUrlScheme( ); if (!alreadyRegistered) { urlTypes.push({ - CFBundleURLName: `${bundleIdentifier}.expo-share-content`, + CFBundleURLName: `${bundleIdentifier}.react-native-share-content`, CFBundleURLSchemes: [scheme], }); } @@ -56,7 +56,7 @@ function registerHostUrlScheme( function getIdentity(config: Parameters[0], options: ResolvedPluginOptions) { const bundleIdentifier = config.ios?.bundleIdentifier; if (!bundleIdentifier) { - throw new Error('[expo-share-content] expo.ios.bundleIdentifier is required.'); + throw new Error('[react-native-share-content] expo.ios.bundleIdentifier is required.'); } return resolveIosIdentifiers( { @@ -124,7 +124,7 @@ function findNativeTarget( if (name !== targetName) continue; if (match) { throw new Error( - `[expo-share-content] Multiple Xcode targets normalize to ${targetName}; refusing ambiguous reconciliation.` + `[react-native-share-content] Multiple Xcode targets normalize to ${targetName}; refusing ambiguous reconciliation.` ); } match = { uuid, pbxNativeTarget: target }; @@ -206,7 +206,7 @@ export function assertCompatibleExtensionTarget( const productType = unquote(target.pbxNativeTarget.productType); if (productType !== 'com.apple.product-type.app-extension') { throw new Error( - `[expo-share-content] Xcode target ${targetName} already exists but is not an app extension.` + `[react-native-share-content] Xcode target ${targetName} already exists but is not an app extension.` ); } @@ -217,14 +217,14 @@ export function assertCompatibleExtensionTarget( ); if (!productReference || fileType !== 'wrapper.app-extension') { throw new Error( - `[expo-share-content] Xcode target ${targetName} has an incompatible product reference.` + `[react-native-share-content] Xcode target ${targetName} has an incompatible product reference.` ); } const productPath = unquote(productReference.path ?? productReference.name); if (productPath && productPath !== `${targetName}.appex`) { throw new Error( - `[expo-share-content] Xcode target ${targetName} product reference path ${productPath} does not match ${targetName}.appex.` + `[react-native-share-content] Xcode target ${targetName} product reference path ${productPath} does not match ${targetName}.appex.` ); } @@ -234,7 +234,7 @@ export function assertCompatibleExtensionTarget( const other = nativeTargets[uuid]; if (other?.productReference === productReferenceUuid) { throw new Error( - `[expo-share-content] Xcode target ${targetName} reuses a product reference owned by another target.` + `[react-native-share-content] Xcode target ${targetName} reuses a product reference owned by another target.` ); } } @@ -250,7 +250,7 @@ export function ensureTargetMembership( (key) => !key.endsWith('_comment') ); if (projectIds.length !== 1) { - throw new Error('[expo-share-content] Cannot reconcile an ambiguous PBXProject root.'); + throw new Error('[react-native-share-content] Cannot reconcile an ambiguous PBXProject root.'); } const root = objects.PBXProject[projectIds[0]!]; root.targets ||= []; @@ -260,7 +260,7 @@ export function ensureTargetMembership( const products = findGroup(project, 'Products'); if (!products) { - throw new Error('[expo-share-content] Cannot resolve the Xcode Products group.'); + throw new Error('[react-native-share-content] Cannot resolve the Xcode Products group.'); } const productReference = target.pbxNativeTarget.productReference; if (!products.group.children.some((entry: any) => entry.value === productReference)) { @@ -285,7 +285,9 @@ export function ensureHostEmbedPhase( const hostNativeTarget = objects.PBXNativeTarget[hostTargetUuid]; if (!hostNativeTarget) { - throw new Error(`[expo-share-content] Cannot resolve host Xcode target ${hostTargetUuid}`); + throw new Error( + `[react-native-share-content] Cannot resolve host Xcode target ${hostTargetUuid}` + ); } const copySection = objects.PBXCopyFilesBuildPhase; @@ -317,7 +319,9 @@ export function ensureHostEmbedPhase( (uuid) => !uuid.endsWith('_comment') && copySection[uuid] === phase ); if (!phaseUuid) { - throw new Error('[expo-share-content] Failed to create host Embed App Extensions phase.'); + throw new Error( + '[react-native-share-content] Failed to create host Embed App Extensions phase.' + ); } } @@ -413,7 +417,7 @@ export function ensureTargetBuildPhase( const nativeTarget = objects.PBXNativeTarget[targetUuid]; if (!nativeTarget) { - throw new Error(`[expo-share-content] Cannot resolve Xcode target ${targetUuid}`); + throw new Error(`[react-native-share-content] Cannot resolve Xcode target ${targetUuid}`); } const phaseReference = nativeTarget.buildPhases.find((entry: any) => { @@ -426,7 +430,7 @@ export function ensureTargetBuildPhase( if (fileNames.length === 0) return; const groupEntry = findGroup(project, groupName); if (!groupEntry) { - throw new Error(`[expo-share-content] Cannot resolve Xcode group ${groupName}`); + throw new Error(`[react-native-share-content] Cannot resolve Xcode group ${groupName}`); } const buildFiles = objects.PBXBuildFile; @@ -490,7 +494,9 @@ export function addExtensionTarget( } const groupEntry = findGroup(project, identifiers.targetName); if (!groupEntry) { - throw new Error(`[expo-share-content] Cannot create Xcode group ${identifiers.targetName}`); + throw new Error( + `[react-native-share-content] Cannot create Xcode group ${identifiers.targetName}` + ); } for (const fileName of allFiles) addFileReference(project, groupEntry.group, fileName); } @@ -504,7 +510,9 @@ export function addExtensionTarget( project.addTarget(identifiers.targetName, 'app_extension', identifiers.targetName); if (!target.uuid) { - throw new Error(`[expo-share-content] Cannot resolve Xcode target ${identifiers.targetName}`); + throw new Error( + `[react-native-share-content] Cannot resolve Xcode target ${identifiers.targetName}` + ); } assertCompatibleExtensionTarget(project, target, identifiers.targetName); ensureTargetMembership(project, target, `${identifiers.targetName}.appex`); @@ -537,13 +545,13 @@ export function addExtensionTarget( const hostTarget = findHostApplicationTarget(project, hostBundleIdentifier); if (!hostTarget) { throw new Error( - '[expo-share-content] Could not find the host application target to embed the Share Extension.' + '[react-native-share-content] Could not find the host application target to embed the Share Extension.' ); } const hostNativeTarget = project.pbxNativeTargetSection()[hostTarget.uuid]; if (!hostNativeTarget) { throw new Error( - '[expo-share-content] Host application target is missing from the Xcode project.' + '[react-native-share-content] Host application target is missing from the Xcode project.' ); } const dependencySection = objects.PBXTargetDependency; diff --git a/plugin/src/options.ts b/plugin/src/options.ts index 01bfb59..4bf03ec 100644 --- a/plugin/src/options.ts +++ b/plugin/src/options.ts @@ -64,7 +64,7 @@ const IOS_ACTIVATION_RULE_TYPES = { function validateMimeTypes(values: readonly string[]): void { for (const value of values) { if (!MIME_TYPE_PATTERN.test(value)) { - throw new Error(`[expo-share-content] Invalid Android MIME type: ${value}`); + throw new Error(`[react-native-share-content] Invalid Android MIME type: ${value}`); } } } @@ -72,7 +72,7 @@ function validateMimeTypes(values: readonly string[]): void { function assertPositiveInteger(value: number, optionName: string): void { if (!Number.isSafeInteger(value) || value <= 0 || value > ANDROID_MANIFEST_INTEGER_MAX) { throw new Error( - `[expo-share-content] ${optionName} must be a positive integer no greater than ${ANDROID_MANIFEST_INTEGER_MAX}.` + `[react-native-share-content] ${optionName} must be a positive integer no greater than ${ANDROID_MANIFEST_INTEGER_MAX}.` ); } } @@ -81,32 +81,36 @@ function validateIosActivationRules(value: IosActivationRules | undefined): void if (value === undefined) return; if (typeof value === 'string') { if (!value.trim()) { - throw new Error('[expo-share-content] iosActivationRules predicate must not be empty.'); + throw new Error( + '[react-native-share-content] iosActivationRules predicate must not be empty.' + ); } if (/\bTRUEPREDICATE\b/i.test(value)) { throw new Error( - '[expo-share-content] iosActivationRules must not contain TRUEPREDICATE in production builds.' + '[react-native-share-content] iosActivationRules must not contain TRUEPREDICATE in production builds.' ); } return; } if (Object.keys(value).length === 0) { - throw new Error('[expo-share-content] iosActivationRules must not be an empty dictionary.'); + throw new Error( + '[react-native-share-content] iosActivationRules must not be an empty dictionary.' + ); } for (const [key, ruleValue] of Object.entries(value)) { const expectedType = IOS_ACTIVATION_RULE_TYPES[key as keyof typeof IOS_ACTIVATION_RULE_TYPES]; if (!expectedType) { - throw new Error(`[expo-share-content] Unsupported iosActivationRules key: ${key}`); + throw new Error(`[react-native-share-content] Unsupported iosActivationRules key: ${key}`); } if (expectedType === 'boolean') { if (typeof ruleValue !== 'boolean') { - throw new Error(`[expo-share-content] ${key} must be a boolean.`); + throw new Error(`[react-native-share-content] ${key} must be a boolean.`); } continue; } if (!Number.isSafeInteger(ruleValue) || Number(ruleValue) <= 0) { - throw new Error(`[expo-share-content] ${key} must be a positive integer.`); + throw new Error(`[react-native-share-content] ${key} must be a positive integer.`); } } } @@ -114,14 +118,14 @@ function validateIosActivationRules(value: IosActivationRules | undefined): void function validateIosOptions(options: ExpoShareContentPluginOptions): void { validateIosActivationRules(options.iosActivationRules); if (options.iosShareExtensionName !== undefined && !options.iosShareExtensionName.trim()) { - throw new Error('[expo-share-content] iosShareExtensionName must not be empty.'); + throw new Error('[react-native-share-content] iosShareExtensionName must not be empty.'); } if ( options.iosDeploymentTarget !== undefined && !/^\d+(?:\.\d+){0,2}$/.test(options.iosDeploymentTarget) ) { throw new Error( - '[expo-share-content] iosDeploymentTarget must be a numeric version such as "16.4".' + '[react-native-share-content] iosDeploymentTarget must be a numeric version such as "16.4".' ); } if (options.iosDeploymentTarget !== undefined) { @@ -129,7 +133,9 @@ function validateIosOptions(options: ExpoShareContentPluginOptions): void { .split('.') .map((part) => Number(part)); if (major < 16 || (major === 16 && minor < 4)) { - throw new Error('[expo-share-content] iosDeploymentTarget must be iOS 16.4 or newer.'); + throw new Error( + '[react-native-share-content] iosDeploymentTarget must be iOS 16.4 or newer.' + ); } } if ( @@ -137,7 +143,7 @@ function validateIosOptions(options: ExpoShareContentPluginOptions): void { !/^group\.[A-Za-z0-9][A-Za-z0-9.-]*$/.test(options.iosAppGroupIdentifier) ) { throw new Error( - '[expo-share-content] iosAppGroupIdentifier must start with "group." and use identifier-safe characters.' + '[react-native-share-content] iosAppGroupIdentifier must start with "group." and use identifier-safe characters.' ); } if ( @@ -145,18 +151,20 @@ function validateIosOptions(options: ExpoShareContentPluginOptions): void { !/^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(options.iosShareExtensionBundleIdentifier) ) { throw new Error( - '[expo-share-content] iosShareExtensionBundleIdentifier is not a valid bundle identifier.' + '[react-native-share-content] iosShareExtensionBundleIdentifier is not a valid bundle identifier.' ); } if (options.iosHostUrlScheme !== undefined) { const scheme = options.iosHostUrlScheme.trim(); if (!scheme) { - throw new Error('[expo-share-content] iosHostUrlScheme must not be empty when provided.'); + throw new Error( + '[react-native-share-content] iosHostUrlScheme must not be empty when provided.' + ); } // Accept bare schemes (myapp) or full URL prefixes (myapp://share). if (!/^[A-Za-z][A-Za-z0-9+.-]*(?::\/\/[\S]*)?$/.test(scheme)) { throw new Error( - '[expo-share-content] iosHostUrlScheme must be a URL scheme such as "myapp" or "myapp://share".' + '[react-native-share-content] iosHostUrlScheme must be a URL scheme such as "myapp" or "myapp://share".' ); } } @@ -189,12 +197,12 @@ export function resolvePluginOptions( ) ) { throw new Error( - '[expo-share-content] iOS activation-rule item counts must not exceed maxSharedItems.' + '[react-native-share-content] iOS activation-rule item counts must not exceed maxSharedItems.' ); } if (maxSharedTotalSize < maxSharedFileSize) { throw new Error( - '[expo-share-content] maxSharedTotalSize must be greater than or equal to maxSharedFileSize.' + '[react-native-share-content] maxSharedTotalSize must be greater than or equal to maxSharedFileSize.' ); }