Closed adam-lynch closed 6 years ago
I don't know how I've only discovered these methods now
Because @kevinsawicki just recently implemented them! 🎉
Ah hey @zeke. I discovered them via your mojibar PR 😄.
Because @kevinsawicki just recently implemented them!
Oh... I thought I saw the commits were from July 😄
Aforementioned PR: https://github.com/muan/mojibar/pull/50
July is recent, no? :)
According to this article: https://blog.timschroeder.net/2012/07/03/the-launch-at-login-sandbox-project/
(With App Sandbox, you cannot create a login item using functions in the LSSharedFileList.h header file. For example, you cannot use the function LSSharedFileListInsertItemURL. Nor can you manipulate the state of launch services, such as by using the function LSRegisterURL.)
So LoginItem API methods are not MAS compatible.
I wonder if auto-launch works.
@zcbenz OK thanks. Do you think it's possible at all? Maybe if it was implemented differently? This article, Modern Login Items, describes a way to achieve it but it requires an extra app (as well as your main app).
If you see https://github.com/electron-userland/electron-builder/issues/756, I thought it might be possible if electron-builder were to give you the extra app* and Electron were to expose an API method to toggle the "user preference."
* Although maybe this extra app could even be provided by Electron itself? I don't know. It would have to be signed though.
@zeke no, see https://github.com/Teamwork/node-auto-launch/issues/43. I'm no expert though.
I'm so confused as to what the Helper does in that blog post - like, why do you need it if they're both sandboxed and have the same privileges?
I didn't look deeply into those articles, it seems that they work by adding a daemon to system with services framework and then start the app from the daemon. It should be possible to provide a builtin solution in Electron.
Sure, but if you've got the privileges to register the daemon, can't you just register the app directly?
Is there a workaround for this?
The idea is there are a few types of applications. MAS compatible Login Items can be either
So you cannot make a proper app (non-agent, with docker icon) a login item. Whether you like it or not, that's how Apple handles it.
I don't know how exactly electron apps are bundled, but login items should be possible by building a cocoa app into Contents/Library/LoginItems
and by tinkering with bundle ids and plists. Then somewhere in the main application, SMLoginItemSetEnabled
must be called.
I'm closing this as won't fix since Apple simply does not provide an official way to do this.
OK too bad, thanks @zcbenz.
Apple does provide an official way and documentation to do this (via the helper app):
To create a login item for your sandboxed app, use the SMLoginItemSetEnabled function (declared in ServiceManagement/SMLoginItem.h) as described in Adding Login Items Using the Service Management Framework.
@fab1an It is great to know there is an API to do that! I'm reopening this issue.
I will try to get this to work and report my findings the next couple of days.
Ok, I am taking time to write this up, because I hope someone will come along and automate it.
The following official way works for me in sandboxed, production-signed .app
bundles. I haven't tried on MAS builds yet, but I think it will work as-is.
Start XCode, Create new mac application, name it MyApp Login Helper
Give it some explicit appId you own like my.electron.app.loginhelper
Enable Sandbox
In Info set key "Application is background-only"
optionally open Storyboard and delete ViewDelegate + Window and the associated files
make sure appDelegate.m
contains the following:
(void)applicationDidFinishLaunching:(NSNotification )aNotification { NSArray pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents]; pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)]; NSString *path = [NSString pathWithComponents:pathComponents]; [[NSWorkspace sharedWorkspace] launchApplication:path]; [NSApp terminate:nil]; }
Export as .app
somewhere and sign it using Mac-Developer Distribution-ID Key. (more on that later)
Add the following to your electron-builder-config:
"extraFiles": [
{
"from": "path-to-your/SuperAwesome Login Helper.app",
"to": "Library/LoginItems/SuperAwesome Login Helper.app"
}
]
Build a dmg
, and directly start the helper:
open dist/mac/YourApp.app/Contents/Library/LoginItems/SuperAwesome Login\ Helper.app
This should start your main-application, the helper merely starts the application three folder up in it's hierarchy.
This should work no matter if your contained app is signed or not, sandboxed, or not.
npm install ffi
to your projectffiLibraryPatched.js
and put in the contents of the PR: https://github.com/benhutchins/node-ffi/blob/64f9707086fcbc42233943a69339b208240ed573/lib/library.jsffiLibraryPatched.js
to point to the ffi
node-moduleffiServiceManagement.js
and put in the following:
module.exports = function SMLoginItemSetEnabled(appId, enabled) {
const ffiLibrary = require("./ffiLibraryPatched.js")
const smLib = ffiLibrary(
"/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement",
{
SMLoginItemSetEnabled: ["bool", ["pointer", "bool"]]
},
null,
{appendExtension: false}
)
const objc = ffiLibrary(null, {
objc_getClass: ["pointer", ["string"]],
sel_registerName: ["pointer", ["string"]]
}),
objcSend_PtrPtrStr = ffiLibrary(null, {
objc_msgSend: ["pointer", ["pointer", "pointer", "string"]]
}),
$C = v => objc.objc_getClass(v),
$R = v => objc.sel_registerName(v);
const appId_cfString = objcSend_PtrPtrStr.objc_msgSend($C("NSString"), $R("stringWithUTF8String:"), appId)
return smLib.SMLoginItemSetEnabled(appId_cfString, enabled);
}
console.log("enabling, sucess: ", SMLoginItemSetEnabled("myapp.loginhelper", true))
console.log("disabling, sucess: ", SMLoginItemSetEnabled("myapp.loginhelper", false))
If everything went smoothly your app should now be able to set the login-helper to start at login which, in turn starts your app and then terminates.
Problem: If the helper is Xcode-exported for MAS a .pkg
comes out, so how to add this to electron bundle?
Possible solution: Generate the Helper without using Xcode somehow and let electron-builder
sign it. codesign
seems to support the --deep
option for signing nested app-bundles, but I haven tried it.
Problem: launchserviced
will pick up the XCode-exported login-helper-app wherever it lies on the filesystem, so you essentially don't know which helper-app get's started at login.
You can find duplicates using this command:
System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dump |grep path|grep "Login\ Helper.app"
You can also try to rebuild launchservices-db using this:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user
Solution: Generate Helper without using Xcode and delete the original.
Problem For some reason my app doesn't auto-start on the first re-login, but the second time I re-login it does. I believe that might be due to launchservices confusion.
Happy hacking.
@fab1an Thanks for the guide. Just to be clear. Is the above guide Mac App Store friendly? Will it pass review?
@holgersindbaek It works yes, my app is on sale by now. Be sure to check out https://github.com/electron-userland/electron-builder/issues/2035 as well, though.
@fab1an Nice. Thanks. I'll check it out.
I'll probably try to implement tomorrow or so. I hope it goes smoothly :-).
@fab1an I'm going through your guide now, but I'm stuck on step 4. Can you be a bit more specific with what I need to do there?
Questions I have:
@fab1an Can you give a hint on the above question?
@fab1an I think I figured out the "ffi" part above. It still doesn't seem to work though.
If I turn on start at login it will return false:
console.log(ffi.SMLoginItemSetEnabled('com.betaunltd.solitaire-game-center-login-helper', true)); // false
I'm not sure how to troubleshoot this. Are you sure it's working for you? You don't need to do LSRegisterURL
(https://forums.developer.apple.com/thread/46883) before you run SMLoginItemSetEnabled
?
This is my library.js
file (the file I modified in the ffi library under /lib/library.js):
https://gist.github.com/holgersindbaek/dcc10fa14d90e3b06de1b8ab6e7ead52
This is my ffiServiceManagement.js
file (the file I've added to the ffi library under /lib/ffiServiceManagement.js):
https://gist.github.com/holgersindbaek/01ad87fcae45f379598ca90b5da2106d
This is my ffi.js
file (the file in the ffi library where I've added the SMLoginItemSetEnabled
library):
https://gist.github.com/holgersindbaek/7467522760736e8ffeebabdcab01dce4
This is my preload.js
where I've added the SMLoginItemSetEnabled
, so I can call it from my app:
https://gist.github.com/holgersindbaek/722edab791661ab16a8f3584e8b1d7ff
When I call SMLoginItemSetEnabled
from my app, it always returns false in the console. Am I missing anything here?
@holgersindbaek Hi Holger, did the API will only work when called from a correctly production-signed bundle, did you try that?
I haven’t tried that yet. Does the app have to be in the application directory as well? Is the only way to confirm that it works, to restart your Mac? Or is there a faster way?
--
All the best
Holger Sindbaek · UI/UX Designer & Web/iOS Programmer · OnlineSolitaire.com ( http://onlinesolitaire.com/ )
On Tue, Oct 17, 2017 at 5:57 AM Fabian Zeindl < Fabian Zeindl ( Fabian Zeindl notifications@github.com ) > wrote:
@holgersindbaek ( https://github.com/holgersindbaek ) Hi Holger, did the API will only work when called from a correctly production-signed bundle, did you try that?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub ( https://github.com/electron/electron/issues/7312#issuecomment-337110703 ) , or mute the thread ( https://github.com/notifications/unsubscribe-auth/AAqqY4dK9BswK8vN1S5fPoBX1SGu_782ks5stCWbgaJpZM4KEWVI ).
If I build a distribution build (MAS build), then I can't open the app locally on my computer. I get the following error:
EXC_CRASH (Code Signature Invalid)
From what I understand, the app is supposed to crash if it's signed with a distribution profile and opened locally on the computer (https://github.com/electron-userland/electron-osx-sign/issues/130).
That still leaves the question of... how do I verify that this actually works?
From what I gather, the only way for me to verify that this works, is to push the app for MAS review, have it go through, download the app and then test it. Am I wrong?
@holgersindbaek You can run codesign -dvvv sample.app to check the signature.
EXC_CRASH (Code Signature Invalid) indicates that the package was modified after signing.
@robinwassen Are you saying that the crash is not to be expected?
From what I understand from this issue - electron-userland/electron-osx-sign#130 - the app is expected to crash when build using distribution signature?
Is there a way to sign the app with a distribution signature and still open it?
@holgersindbaek No the app doesn't have to be in the application directory, but it has to be sandboxed and signed. The helper needs proper permissions as well: https://github.com/electron-userland/electron-builder/issues/2035.
@fab1an Does it need to be signed with a distribution profile for the app store?
I've tried to get it to work, but signing both the main app and the helper app with a developer profile, but that didn't work. I got false
back in the console when I tried to call SMLoginItemSetEnabled
. Is it the same for you? Will it only work when signed for the MAS?
The helper app seems to have the correct permissions:
➜ assets git:(master) ✗ codesign -d --entitlements :- Solitaire\ Game\ Center\ Login\ Helper.app
Executable=/Users/holgersindbaek/MegaDrive/BetaUnltd/SolitaireGameCenter/Programming/SolitaireGameCenter/src/electron/assets/Solitaire Game Center Login Helper.app/Contents/MacOS/Solitaire Game Center Login Helper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
@holgersindbaek Yes, you need a profile. It works for 3rd-party-signed applications as well (Mac Apps, not MAS). Did you check whether all your appIds are registered correctly?
@fab1an If I sign my application with a 3rd party signature, then the app crashes as documented above. Can you share you "build" section from your package.json?
My main app has the app ID "com.betaunltd.solitaire-game-center", which is registered in my developer portal. My helper app has the app ID "com.betaunltd.solitaire-game-center.login-helper". I haven't registered that in my developer portal. Is that necessary?
@holgersindbaek Sorry, I can't share my build, as I don't use JavaScript, but Gradle instead. It's not easily reproducible for others.
Yes, you have to register your helper app as well.
@holgersindbaek You don't have to wait for MAS review. Are you able to build a production signed MAC-application? Not MAS, but direct distribution, so that other Macs can open that app.
With such an application it will work, and you can test it locally. Here is the relevant part of my electron-build process:
const electronBuilder = require("electron-builder")
let languages = ['en'];
electronBuilder.build({
config: {
npmRebuild: false,
appId: "com.fab1an.screentime",
mac: {
category: "public.app-category.productivity",
target: [
"dmg",
"mas"
],
extendInfo: {
LSUIElement: 1
},
type: "distribution"
},
files: [
"package.json",
"node_modules/**/*",
{
from: "build/electron_pack/",
to: ""
}
],
extraFiles: [
{
from: "ScreenTime Login Helper/export/ScreenTime Login Helper.app",
to: "Library/LoginItems/ScreenTime Login Helper.app"
}
],
afterPack: context => {
return new Promise((res, rej) => {
let glob = require('glob'),
del = require('del');
glob(`${context.appOutDir}/${context.packager.appInfo.productName}.app/Contents/Resources/!(${languages.join('|')}).lproj`, (err, files) => {
if (err) return rej(err);
del(files).then(res, rej);
});
});
}
}a
The afterPack
stuff is just for deleting extra languages.
I have a
Hmm, ok. I'll try to create a Mac production provisioning profile as well then. Is it important that the helper app and the main app is signed with the same signature?
I seem to remember seeing the LSUIElement
in connection with SMLoginItemSetEnabled
some places. What exactly is it that it does? And do you think it is important in connection with all this?
Do that. I don't know whether the need to have the same signature, please report back if you find something out.
LSUIElement
simply hides the dock icon (i have a system-tray only app). Doesn't have any effect on this.
Is this implemented now? What do we need to do to be able to use the login helper?
Reopened due to #10856 being reverted by #11140 :P
See https://github.com/electron/electron/pull/10856#issuecomment-344994847 for details
@ckerr should we close this issue since https://github.com/electron/electron/pull/11144 is merged?
Closed with #11144.
Thanks everyone.
👏 well done & thanks
I want to point some attention to the fact that after https://github.com/electron/electron/pull/15010 LoginItem API is not well implemented for MAS builds.
The openAtLogin
settings is the only one available now, but we need at least wasOpenedAtLogin
flag for MAS builds.
Also there is an issue now with the lack of app in the login items list in System Preferences
I don't know how I've only discovered these methods now 😄 but anyway...
Would an app get rejected by the MAS if it used
app.setLoginItemSettings
for example? The Mac App Store submission guide doesn't list it as a limitation but I would assume that it isn't MAS-friendly. Although, it would be great if it was compatible. I guess it depends on how it's implemented underneath (i.e. whether it reaches out of the sandbox or not, etc.)cc @kevinsawicki
Related: https://github.com/Teamwork/node-auto-launch/issues/43