alinz / react-native-share-extension

react-native as an engine to drive share extension
MIT License
763 stars 398 forks source link

Invalid image path obtained from ShareExtension.data #89

Open BigPun86 opened 6 years ago

BigPun86 commented 6 years ago

When i navigate to the Host Application and pass the image url which i have obtained from value of ShareExtension then in my real device the imgPath wont work! I actually cannot use the url which looks like this:

/private/var/mobile/Containers/Data/PluginKitPlugin/09BD2F36-975D-4DB4-80FD-2446799A48AE/tmp/RNSE_TEMP_IMG.png

I have tried to check if the imgPath is available. I did this with react-native-fs and there i get for the imgPath the error that the file is not there.

Everything works great on Android (Simulator & RealDevice) as well as on the iOS Simulator. But it fails on iOS RealDevice.

Receiving ShareData:

try {
    const { type, value } = await ShareExtension.data();
    let error;
    const url = `shareExtensionApp://shareImage/${value}`;
    ShareExtension.openURL(url);
    ShareExtension.close();
} catch (e) {
    Alert.alert(null, e);
}

Use value for my Image component:

<Image source={{uri: value}}/>

isaachinman commented 6 years ago

I just ran into this issue myself when testing on a real iOS device. Functionality works perfectly on iOS simulator and Android, but not on a real iOS device (iPhone 6, iOS 11.3).

My received share path looks virtually identical to OP's (eg /private/var/mobile/Containers/...).

@BigPun86 Did you find a way to solve this?

From my limited understanding, this has to do with app containers/permissions.

BigPun86 commented 6 years ago

@isaachinman i forked this project and and dis some stuff in the iOS part, which finally works not. You can have a look here: https://github.com/BigPun86/react-native-share-extension/tree/master/ios

You will need to enable App Groups for both targets (MainApp and Share Extension)

isaachinman commented 6 years ago

@BigPun86 Thanks! Lot of work getting this project to actually work cross-platform...

What was your methodology? Are you copying files to a temporary shared dir between the two apps (via App Group), or did you find a way to share the path by itself without copying?

It would be super helpful if you can open a PR, at least so people like myself can see the diff. I doubt it'll ever get merged, based on how this project is managed...

BigPun86 commented 6 years ago

@isaachinman well i am actually creating a directory related to the app group and then i combine the app group path/url together with my file path/url. I am not good in explaining, but you can have a look at the ShareMaster.h/m => Readme.md and this part https://github.com/BigPun86/react-native-share-extension/blob/master/ios/ReactNativeShareExtension.h#L7

I hope this helps you, it took me 2 weeks to completely integrate the share-extension for ios and android :D

isaachinman commented 6 years ago

@BigPun86 With app groups in place, it's actually way easier to achieve this than the way you're doing it.

You only need app groups and react-native-fs to accomplish it:

const data = await ShareExtension.data()

if (Platform.OS === 'ios') {
    const appGroupPath = await RNFS.pathForGroup('group.com.myapp')

    await Promise.all(data.map(async (image) => {
        const filename = path.basename(image.value)
        const destination = `${appGroupPath}/${filename}`
        await RNFS.copyFile(image.value, destination)
    }))
}

For any future readers that might need help with this, just let me know.

BigPun86 commented 6 years ago

@isaachinman thanks a lot, i tried it with react-native-fs, maybe i have missed something. But thanks, i will update my code and test your snippet :)

isaachinman commented 6 years ago

Cool! The main takeaway here is that what we both want to do is fully possible with react-native-share-extension, no fork needed. (Although I'm actually using a different fork to allow multi-item sharing.)

By the way, I got this idea from #73, which should definitely be merged into master.

ahmedjamshed commented 6 years ago

@isaachinman my code is stuck at this line in share extension. await RNFS.copyFile(image.value, destination)

I am able to show the image from path in share extenion but not in main app. So I decided to configure app group. I only configured the app group in share extension for now and I am able to get the app group path from this line.

const appGroupPath = await RNFS.pathForGroup('group.com.myapp')

My problem is that when i try to execute this line

await RNFS.copyFile(image.value, destination)

it never fullfills the promise and the lines below this line never get executed although the extension does not crash

Here is my full code

async componentDidMount() {
try {
    const { type, value } = await ShareExtension.data()
    const appGroupPath = await RNFS.pathForGroup('group.com.myapp')
    const filename = path.basename(value)
    const destination = `${appGroupPath}/${filename}`
    await RNFS.copyFile(value, destination) // even tried with seprate then/catch block
   // lines never get executed, promise never gets fulfilled
   ShareExtension.openUrl('myapp://${value}') // gets executed when remove this line
  //await RNFS.copyFile(value, destination)
} catch(e) {
   console.log(e.message) // No error returned here because changing state here in actual code
}
}

More Info:

What could be the problem? Can you guide me? BTW sorry for bad formatting.

isaachinman commented 6 years ago

@ahmedjamshed First, you should always format code when you write in a GitHub issue. Second, how could I possibly help without any error or any information whatsoever? If you post more information here in the proper way, I am happy to help you.

ahmedjamshed commented 6 years ago

@isaachinman Sorry for bad formatting. I have updated my previous comment,

isaachinman commented 6 years ago

The whole point of an app group in this case is that it creates a shared directory between your share extension and main app, where both processes can access (read/write).

Sounds like you need to add your main app to the app group, at the very least.

That being said, if copyFile is hanging - ie not throwing and not resolving - it actually sounds like a RNFS bug...

Can you print out image.value and destination?

ahmedjamshed commented 6 years ago

Can you print out image.value and destination?

Yes, I am able to show the value and destination on extenion UI by calling setState

isaachinman commented 6 years ago

I meant, can you write it here?

ahmedjamshed commented 6 years ago

@isaachinman Thanks for helping. My app groups weren't properly configured due to some Bundle Identifier issue. Changing bundle identifier and cleaning the code solved my problem.

isaachinman commented 6 years ago

Ah, good to hear.

radreamer commented 5 years ago

I'm facing with an error while copying a file to an app group destination from iOS Files app (/private/var/mobile/Library/Mobile%2520Documents/com~apple~CloudDocs/Documents/<fileName>):

Error: the file <fileName> couldn't be opened because there is no such file.

but everything works perfectly when I copying photos/videos from my gallery, which have the further path: /private/var/mobile/Containers/Data/PluginKitPlugin/<uuid>/tmp/RNSE_TEMP_IMG.png

I`m using RNFetchBlob but experienced the same behavior when tried RNFS. My code snippet:

async componentDidMount() {
    try {
      const { value } = await ShareExtension.data()

      if (Platform.OS === 'ios') {
        const appGroupPath = await RNFetchBlob.fs.pathForAppGroup('<my-group-name>')
        const destination = `${appGroupPath}/${value.split('/').slice(-1)[0]}`

        await RNFetchBlob.fs.cp(value.split('file://').slice(-1)[0], destination)
        await ShareExtension.openURL(`<my-deep-link>/${destination}`)

        this.closeExtension()
      } else if (Platform.OS === 'android') {
        await Linking.openURL(`<my-deep-link>/${value}`)
        this.closeExtension()
      }
    } catch () {
      this.closeExtension()
    }
  }

Is it possible to manipulate (copy to app group dir) with files which are hosted under this path /private/var/mobile/Library/Mobile%2520Documents/com~apple~CloudDocs/?

isaachinman commented 5 years ago

Hi @radreamer, if you're having problems accessing a file when you know it definitely exists at that location, it's almost certainly related to permissions.

I have set up a share extension in the past that copies files to an app group dir and then passes the location onto the main app via query params - it's definitely possible.

Perhaps this also has something to do with the CloudDocs component? Not sure about that.

oberlaender commented 5 years ago

Could also be that the filetype is not handled by the native code on iOS, it seem only images, urls and 'text/plain' are supported.

#define URL_IDENTIFIER @"public.url" #define IMAGE_IDENTIFIER @"public.image" #define TEXT_IDENTIFIER (NSString *)kUTTypePlainText

see ReactNativeShareExtension.m

radreamer commented 5 years ago

@isaachinman @oberlaender thanks for responses, I tried to share an image from iCloud (through Files app) and the further code

const { type, value } = await ShareExtension.data()
console.log(type, value)

prints (!wrong type):

text/plain file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Photos/<filename>.jpg

and then copying failed with:

Error: the file <fileName>.jpg couldn't be opened because there is no such file.

so I think, that the problem isn't with file types

radreamer commented 5 years ago

@oberlaender and I don't get the difference between URL and text? both have almost similar handlers for providers in extractDataFromContext method (urlProvider and textProvider) and what is more important – returns same type on ShareExtension.data().

From the point of view of a react-native app there is no difference between them, am I right?

oberlaender commented 5 years ago

Both providers return a string and the type text/plain. The url part just makes sure the full url is returned.

isaachinman commented 5 years ago

@radreamer I don't fully understand your problem, but it seems like you're getting valid file paths without issue. Your issue is related to read permissions. Where are you getting the no such file error? In the share extension, or in your main app?

Can you provide a reproducible example so that we might be able to help you?

radreamer commented 5 years ago

@isaachinman yes, I'm getting valid file paths, but then I get no such file when try to copy files:

RNFS.copyFile(value.split('file://').slice(-1)[0], destination)

Can you try to share an image from iCloud (via Files app) through your share extension?

isaachinman commented 5 years ago

@radreamer I don't think your problem has anything to do with react-native-share-extension, if you're getting valid file paths. I'm not an iOS dev by trade, but have you done some Googling and read some docs?

frenberg commented 5 years ago

I see this as well... If you look closely att your file path /private/var/mobile/Library/Mobile%2520Documents/com~apple~CloudDocs/Documents/<fileName>

That Mobile%2520Documents should actually be ...Mobile Documents... Just saw this myself so not sure where this problem arises yet.

isaachinman commented 5 years ago

@frenberg That's a great point. I've run into URI encoding issues before, but always on Android, not iOS.

This URI is encoded twice:

/private/var/mobile/Library/Mobile%2520Documents/com~apple~CloudDocs/Documents/<fileName>

If I run:

decodeURIComponent(decodeURIComponent('/private/var/mobile/Library/Mobile%2520Documents/com~apple~CloudDocs/Documents/<fileName>'))

I get:

"/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Documents/<fileName>"
radreamer commented 5 years ago

@frenberg @isaachinman now it works, thanks for your help!

aryamanm commented 4 years ago

@isaachinman The file “XXX.JPG” couldn’t be opened because you don’t have permission to view it.

Any idea how to fix this?