Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support LoginItem API in Mac App Store builds #7312

Closed
adam-lynch opened this issue Sep 22, 2016 · 43 comments
Closed

Support LoginItem API in Mac App Store builds #7312

adam-lynch opened this issue Sep 22, 2016 · 43 comments

Comments

@adam-lynch
Copy link
Contributor

  • Electron version: N/A
  • Operating system: Mac or Windows

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: Teamwork/node-auto-launch#43

@zeke
Copy link
Contributor

zeke commented Sep 22, 2016

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

Because @kevinsawicki just recently implemented them! 🎉

@adam-lynch
Copy link
Contributor Author

adam-lynch commented Sep 22, 2016

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
Copy link
Contributor

zeke commented Sep 22, 2016

Aforementioned PR: muan/mojibar#50

July is recent, no? :)

@zcbenz
Copy link
Member

zcbenz commented Sep 26, 2016

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
Copy link
Contributor

zeke commented Sep 26, 2016

I wonder if auto-launch works.

@adam-lynch
Copy link
Contributor Author

@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 electron-userland/electron-builder#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 Teamwork/node-auto-launch#43. I'm no expert though.

@anaisbetts
Copy link
Contributor

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
Copy link
Member

zcbenz commented Sep 28, 2016

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
Copy link
Contributor

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

@kevinsawicki kevinsawicki changed the title App: Are LoginItem API methods Mac App Store compatible? Support LoginItem API in Mac App Store builds Sep 29, 2016
@anilanar
Copy link

anilanar commented May 6, 2017

Is there a workaround for this?

The idea is there are a few types of applications. MAS compatible Login Items can be either

  • agent apps: can display UI, no docker icon, does not appear in App switcher. Typically interacted via custom user input such as keyboard shortcuts or tray icons.
  • background apps: daemons, no UI.

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
Copy link
Member

zcbenz commented May 30, 2017

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

@zcbenz zcbenz closed this as completed May 30, 2017
@adam-lynch
Copy link
Contributor Author

OK too bad, thanks @zcbenz.

@fab1an
Copy link
Contributor

fab1an commented Aug 14, 2017

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
Copy link
Member

zcbenz commented Aug 14, 2017

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

@zcbenz zcbenz reopened this Aug 14, 2017
@fab1an
Copy link
Contributor

fab1an commented Aug 15, 2017

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

@fab1an
Copy link
Contributor

fab1an commented Aug 16, 2017

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

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

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

  • Call the function in your app:
console.log("enabling, sucess: ", SMLoginItemSetEnabled("myapp.loginhelper", true))
console.log("disabling, sucess: ", SMLoginItemSetEnabled("myapp.loginhelper", false))
  • Enable Sandbox for you mac-app and build it.

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:

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

  • Tipp: Double-check your appIds and entitlements

Happy hacking.

@holgersindbaek
Copy link

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

@fab1an
Copy link
Contributor

fab1an commented Sep 11, 2017

@holgersindbaek It works yes, my app is on sale by now. Be sure to check out electron-userland/electron-builder#2035 as well, though.

@holgersindbaek
Copy link

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

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

@holgersindbaek
Copy link

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

  • Should I install "ffi" by doing "npm install ffi --save"?
  • Should I create "ffiLibraryPatched.js" under node_modules/ffi/lib?
  • Which requires is it that I need to fix in "ffiLibraryPatched.js"?
  • Where do I find the "ffiServiceManagement.js"?

@fab1an
Copy link
Contributor

fab1an commented Oct 17, 2017

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

@holgersindbaek
Copy link

holgersindbaek commented Oct 17, 2017 via email

@holgersindbaek
Copy link

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 (electron/osx-sign#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
Copy link
Contributor

@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
Copy link

@robinwassen Are you saying that the crash is not to be expected?

From what I understand from this issue - 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
Copy link
Contributor

fab1an commented Oct 17, 2017

@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: electron-userland/electron-builder#2035.

@holgersindbaek
Copy link

holgersindbaek commented Oct 17, 2017

@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
Copy link
Contributor

fab1an commented Oct 17, 2017

@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
Copy link

@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
Copy link
Contributor

fab1an commented Oct 17, 2017

@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
Copy link
Contributor

fab1an commented Oct 18, 2017

@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

  • Mac Production Provisioning Profile and a
  • MAS Production Provisioning Profile
    in my root folder.

@holgersindbaek
Copy link

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
Copy link
Contributor

fab1an commented Oct 18, 2017

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
Copy link
Contributor

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

@ckerr ckerr reopened this Nov 16, 2017
@ckerr
Copy link
Member

ckerr commented Nov 16, 2017

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

See #10856 (comment) for details

@sethlu
Copy link
Contributor

sethlu commented Nov 28, 2017

@ckerr should we close this issue since #11144 is merged?

@zcbenz
Copy link
Member

zcbenz commented Nov 28, 2017

Closed with #11144.

@zcbenz zcbenz closed this as completed Nov 28, 2017
@fab1an
Copy link
Contributor

fab1an commented Nov 28, 2017

Thanks everyone.

@adam-lynch
Copy link
Contributor Author

👏 well done & thanks

@Mizzick
Copy link

Mizzick commented Mar 11, 2019

I want to point some attention to the fact that after #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
Copy link

ethielknd commented Jan 16, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests