JaneaSystems / nodejs-mobile

Full-fledged Node.js on Android and iOS
https://code.janeasystems.com/nodejs-mobile
Other
2.55k stars 184 forks source link

Error writing within nodejs-assets or nodejs-project dir iOS #3

Open scriptjs opened 6 years ago

scriptjs commented 6 years ago

I created a var folder locally in node assets and added this to may main.js within my nodejs-project. This puts the var folder at the same level as the nodejs-project

var fs = require('fs');
var varDir = path.join(__dirname, '..', 'var')
fs.writeFile(varDir, "Test write", function(err) {
    if(err) return console.log(err);
    console.log("The file was written");
})

I receive an errorno: -1, 'EPERM: operation not permitted' error when attempting to write to it. I also tried writing to nodejs-project with the same effect. I'd like to add files into the node project folder. For stashing files, the general director in iOS is

/var/mobile/Applications/<myappid>/Documents 

but attempting a write here does not work either:

var appId = (__dirname).split(path.sep)[6]
var docsDir = path.join(path.sep,'var', 'mobile', 'Applications', appId, 'Documents')
console.log(`docsDir: ${docsDir}`)

fs.writeFile(path.join(docsDir, 'test.txt'), "Test write", function(err) {
    if(err) return console.log(err);
    console.log("The file was written");
});

I receive the following error:

{ [Error: ENOENT: no such file or directory, open '/var/mobile/Applications/<myappid>/Documents/test.txt']
  message: 'ENOENT: no such file or directory, open \'/var/mobile/Applications/<myappid>/Documents/test.txt\'',
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/var/mobile/Applications/<myappid>/Documents/test.txt' }
jaimecbernardo commented 6 years ago

@scriptjs Thank you for reporting this issue. Just to be sure, are you using the https://github.com/janeasystems/nodejs-mobile-react-native plugin? It seems like so.

There's the issue on iOS that you can't write in the Bundle path. Each installation of the application will change the Documents folder (it has a different than the Bundle's id too).

Currently, one way to get around this is to pass the writable path as a message from react-native to Node.js.

I've just run a test using the package react-native-fs to get the writable documents path.

In react-native:

import nodejs from 'nodejs-mobile-react-native';
var RNFS = require('react-native-fs');

...

  componentWillMount()
  {
    nodejs.start();
    nodejs.channel.send(RNFS.DocumentDirectoryPath);
  }

In node's main.js:

var rn_bridge = require('rn-bridge');
var fs = require('fs');
var path = require('path');
console.log('NODE : Current dir is: ',__dirname);

...

rn_bridge.channel.on('message', (msg) => {
     var docsDir=msg;
     console.log(`docsDir: ${msg}`);
     fs.writeFile(path.join(docsDir, 'test.txt'), "Test write", 
                                function(err) {
                                  if(err) return console.log(err);
                                  console.log("The file was written");
                                });
});

I get The file was written, and verified that the is different in both cases, due to iOS App sandbox process.

The bridge is there as a general purpose message system between the UI and the Node.js backend. You can build your own communication protocol on top of it.

This is still an experimental project and functions to get the device's paths may end up getting added somewhere in the bridge components. What are your thoughts on this?

quinton-ashley commented 6 years ago

So there's no way as of now to use the basic node.js fs module to write a file? For compatibility the basic node.js fs module should work and relative file paths should be automatically fulfilled like on desktkop right? Perhaps paths used in node could be translated to the "real" absolute paths iOS uses behind the scenes.

scriptjs commented 6 years ago

@jaimecbernardo I wanted to thank you for your help.

@quinton-ashley I think from above, it is possible to write but only to specific locations. react-native-fs needs to be added that will give you access to a Documents directory that should be writable.

iOS is not a desktop and its file structure is sandboxed for each app. What I observed from react native is that the appId reported by the app in react native is different than the appid reported in the running nodejs.

That said I have not tested all filesystem actions to verify full fs compatibility when have write permission. I ran into a second issue in that when breaking the nodejs app into multiple files other than a single main.js, there was an issue running it. This may have been an isolated circumstance but I need to work with this a bit more to determine where the problems are.

@jaimecbernardo Is there a some visible testing against a ci endpoint so we can see whether the core node is functional and where the issues might be? Thank you again.

jaimecbernardo commented 6 years ago

Hi, @quinton-ashley , it's as @scriptjs says, in iOS your app is given a sandbox environment to operate, where you have different writable paths with different purposes, like Documents, Application Support, Cache, Temporary Files.

@scriptjs , we don't have CI yet. One thing we can be sure of is that on iOS starting new processes is not allowed by the OS.

mohlsen commented 6 years ago

I would assume that some of the functionality would be mapped by this project, such as os.tmpdir mapping to the the temporary file locations provided by both iOS and Android. I would then expect additional functions to expose the other various platform specific locations.

lukaskollmer commented 6 years ago

@mohlsen os.tmpdir on iOS should already point at the temporary folder in the app's bundle

jaimecbernardo commented 6 years ago

Thanks for the discussion so far.

In iOS, os.tmpdir works because the TMPDIR environment variable is set, and it's how os.tmpdir works: https://github.com/janeasystems/nodejs-mobile/blob/dc0b96085590059bec10ca18ce0dd479f74b2f20/lib/os.js#L108-L111

In Android, you can simulate similar behavior by setting the environment variable TMPDIR as well. I tested it by using Os.setenv before starting Node.js in an Activity:

import android.system.Os;

...

Os.setenv("TMPDIR",getApplicationContext().getCacheDir().getAbsolutePath(),true);

In this example, it is set to the Application's Cache directory, which seems to be the most appropriate here. Adding this behavior by default and adding further paths is still pending design.

Further discussion contributions are welcome for the API design choices.

marsbible commented 5 years ago

@jaimecbernardo Any progress of this issue? Thanks.

quinton-ashley commented 5 years ago

Woah I forgot about this project. I'd also like to know if any progress has been made. Are relative paths fulfilled for reading files yet? Is the tmp directory for writing files set appropriately by default?

jaimecbernardo commented 5 years ago

Hi @marsbible and @quinton-ashley ,

There have been API changes that are being tested in the Cordova plugin, which includes a function to get the "Documents" or "Files" directory for each platform: https://github.com/janeasystems/nodejs-mobile-cordova/tree/d71a51aae7ae5a01c4970147d67537320106145f#cordovaappdatadir

On Android, it also sets the Cache Directory as the temporary directory, since the Android OS doesn't really have a directory for temporary files. This may or may not be desirable, since the temporary directory isn't quite the same as a caches directory. https://github.com/janeasystems/nodejs-mobile-cordova/blob/d71a51aae7ae5a01c4970147d67537320106145f/src/android/java/com/janeasystems/cdvnodejsmobile/NodeJS.java#L86

We plan to apply similar API changes to the react-native plugin as well. Either way, the general case is that the folders the application expects to be used can be passed through a user defined communication method from either native code or the UI engine.