electron / electron

:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS
https://electronjs.org
MIT License
113.81k stars 15.32k forks source link

Support LoginItem API in Mac App Store builds #7312

Closed adam-lynch closed 6 years ago

adam-lynch commented 8 years ago

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

zeke commented 8 years ago

I don't know how I've only discovered these methods now

Because @kevinsawicki just recently implemented them! 🎉

adam-lynch commented 8 years ago

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 😄

zeke commented 8 years ago

Aforementioned PR: https://github.com/muan/mojibar/pull/50

July is recent, no? :)

zcbenz commented 8 years ago

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.

zeke commented 8 years ago

I wonder if auto-launch works.

adam-lynch commented 8 years ago

@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.

anaisbetts commented 8 years ago

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?

zcbenz commented 8 years ago

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.

anaisbetts commented 8 years ago

Sure, but if you've got the privileges to register the daemon, can't you just register the app directly?

anilanar commented 7 years ago

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, SMLoginItemSetEnabledmust be called.

zcbenz commented 7 years ago

I'm closing this as won't fix since Apple simply does not provide an official way to do this.

adam-lynch commented 7 years ago

OK too bad, thanks @zcbenz.

fab1an commented 7 years ago

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.

- Designing for App Sandbox (developer.apple.com)

zcbenz commented 7 years ago

@fab1an It is great to know there is an API to do that! I'm reopening this issue.

fab1an commented 7 years ago

I will try to get this to work and report my findings the next couple of days.

fab1an commented 7 years ago

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.

Step 1: Helper App

Step 2: Include the helper in your app

Add the following to your electron-builder-config:

"extraFiles": [
            {
                "from": "path-to-your/SuperAwesome Login Helper.app",
                "to": "Library/LoginItems/SuperAwesome Login Helper.app"
            }
        ]

Step 3: Verify it works

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.

Step 4: Call it from your mainApp.


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);
}

Step 5: Build your app

Step 6: Success

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.

Troubleshooting and known problems:

Happy hacking.

holgersindbaek commented 7 years ago

@fab1an Thanks for the guide. Just to be clear. Is the above guide Mac App Store friendly? Will it pass review?

fab1an commented 7 years ago

@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.

holgersindbaek commented 7 years ago

@fab1an Nice. Thanks. I'll check it out.

I'll probably try to implement tomorrow or so. I hope it goes smoothly :-).

holgersindbaek commented 6 years ago

@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:

holgersindbaek commented 6 years ago

@fab1an Can you give a hint on the above question?

holgersindbaek commented 6 years ago

@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?

fab1an commented 6 years ago

@holgersindbaek Hi Holger, did the API will only work when called from a correctly production-signed bundle, did you try that?

holgersindbaek commented 6 years ago

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 ).

holgersindbaek commented 6 years ago

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?

robinwassen commented 6 years ago

@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.

holgersindbaek commented 6 years ago

@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?

fab1an commented 6 years ago

@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.

holgersindbaek commented 6 years ago

@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>
fab1an commented 6 years ago

@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?

holgersindbaek commented 6 years ago

@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?

fab1an commented 6 years ago

@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.

fab1an commented 6 years ago

@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

holgersindbaek commented 6 years ago

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?

fab1an commented 6 years ago

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.

tom-james-watson commented 6 years ago

Is this implemented now? What do we need to do to be able to use the login helper?

ckerr commented 6 years ago

Reopened due to #10856 being reverted by #11140 :P

See https://github.com/electron/electron/pull/10856#issuecomment-344994847 for details

sethlu commented 6 years ago

@ckerr should we close this issue since https://github.com/electron/electron/pull/11144 is merged?

zcbenz commented 6 years ago

Closed with #11144.

fab1an commented 6 years ago

Thanks everyone.

adam-lynch commented 6 years ago

👏 well done & thanks

Mizzick commented 5 years ago

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

ethielknd commented 1 year ago

It seems that autolaunch for MAS is not working, I filed an issue with the details I could find