Access to macOS Security-Scoped bookmarks in an electron application
Homepage Repository npm Download
npm install electron-bookmarks@0.2.4
Security-Scoped Bookmarks
inside of an electron application.app-scoped
bookmarks at this time. (document-scoped
bookmarks are planned, but their implementation may be more difficult).nodobjc
for native Objective-C runtime bindings. I'm in the process of releasing an app on the App Store that uses this module and will update this as soon as I know.
Please note this is experimental software, and is not currently ready for the production environment. It is currently under active development and its API may change from version to version without notice, until this reaches version 1.0.0
Due to the way Objective-C is bridged to the Node process, you MUST use this module in electron
's main process. It will not work in the renderer process.
Super easy.
npm install --save electron-bookmarks
This module was created to be a drop-in-replacement for electron's dialog
module. In order to create a security-scoped bookmark you have to use Use Apple's Powerbox
API, which is used transparently in the background by NSOpenPanel
(and NSSavePanel
). Thus, the need to re-create electron's dialog
module in order to have access to the NSURL
class returned by it.
You must have the correct entitlements (either com.apple.security.files.bookmarks.document-scope
or com.apple.security.files.bookmarks.app-scope
in conjunction with com.apple.security.files.user-selected.read-write
) in your signed electron app (see here for more information), it must have been already packaged for the mas platform
, and of course, this only runs on macOS
.
Change this:
let win = new BrowserWindow();
dialog.showOpenDialog(win, { /* ... */ }, (filenames) => {
console.log(filenames); // [ '/path/to/file/' ]
});
To this:
const bookmarks = require('electron-bookmarks');
bookmarks.showOpenDialog(win, { bookmarkType: 'app', /* ... */ }, (filenames, bookmarks) => {
console.log(filenames); // [ '/path/to/file/' ]
console.log(bookmarks); // { keys: [ 'bookmark::file:///path/to/file/' ], ... }
});
And this:
fs.writeFile('/path/to/file', 'foo', 'utf8', function (err) {
if (err) throw err; // Error: EPERM: operation not permitted, access '/path/to/file'
else {
// ...
}
});
To this:
const bookmarks = require('electron-bookmarks');
bookmarks.open(myBookmark, function (allowedPath, close) {
fs.writeFile('/path/to/file', 'foo', 'utf8', function (err) {
if (err) throw err; // null
else {
// Yay! We have access outside the sandbox!
// We can read/write to this file.
// ...
// Once finished, the bookmark *MUST* be closed!
close();
}
});
});
Note that initialisation of this module may take some time, since it has to bridge Node to Objective-C and setup all the runtime calls. For this reason bookmarks.init()
has been provided, so you can initialise it in your app at a convenient time. If you don't call this, bookmarks
will be initialised the first time you use any of its methods.
bookmarks.showOpenDialog(window, options[, callback])
In order to use this you must do two things:
bookmarkType: 'app'
. Default is "app"
(the value "document"
will - hopefully - be supported in the future).showOpenDialog
's asynchronous API. There is currently no support for the synchronous API (nor will there be).Usually electron's dialog.showOpenDialog
will return an array of filenames. electron-bookmarks
returns the same array but with an additional argument bookmarks
.
bookmarks.keys
tells you which bookmarks have just been saved to NSUserDefaults
as NSData
objects. These bookmarks are accessible across app restarts and allow your app to access files outside its sandbox provided you use the APIs correctly. Use these keys
in bookmarks.open(key, ...)
.
bookmarks.showSaveDialog()
Important!
bookmarks.showSaveDialog()
will create the file at the path you select in the dialog, since a bookmark cannot be created without a file. Therefore, remember that after using bookmarks.showSaveDialog()
you will be reading or writing to an already existing file.
Otherwise the same as showOpenDialog, you must:
bookmarkType: 'app'
. Default is "app"
(the value "document"
will - hopefully - be supported in the future).showOpenDialog
's asynchronous API. There is currently no support for the synchronous API (nor will there be).Usually electron's dialog.showSaveDialog
will return a path. electron-bookmarks
returns the same path but with an additional argument bookmark
.
bookmark.key
is the key that has just been saved to NSUserDefaults
as an NSData
object. This bookmark is accessible across app restarts and allow your app to access the file outside its sandbox provided you use the APIs correctly. Use this key
in bookmarks.open(key, ...)
.
bookmarks.open(key, callback)
key
is a key returned from bookmarks.showOpenDialog
or from bookmarks.list
.callback(allowedPath, close)
allowedPath
is the path you must use in order to access your file.close
IMPORTANT: this function MUST be called once you have finished using the file! If you do not remember to close this, kernel resources will be leaked and your app will lose its ability to reach outside the sandbox completely, until your app is restarted.bookmarks.list()
This will return an array of all the keys that you've saved with electron-bookmarks
. These can be used in order to gain access outside your sandbox.
bookmarks.deleteOne(key)
Simply delete a bookmark by its key.
Returns undefined
;
bookmarks.deleteAll()
Removes all bookmarks associated with your app.
Returns undefined
;
bookmarks.init()
Links the runtime Objective-C to Node. May take up to a second to complete.
MIT