Announcing meteor-desktop

hey @omega or anyone else who’s used this project, I’ve got a quick question

I’ve got basic IPC interactions working with the Desktop and Module classes, but it seems like desktop-specific code is required to live within .desktop/.

Is it possible to place desktop-specific code that uses the Module api in files outside .desktop, for example in /src/imports/foobar.js but then import it from within .desktop/? Or does the file have to actually existing within .desktop/?

Hi, does the update package work with this?

1 Like

Does it have React?
I mean this is so good but react is the way to go for many of us

1 Like

It just runs your Meteor client code inside an Electron renderer process, you’re free to use any UI framework you wish. I’ve been using Vue in it but React would work too

2 Likes

very cool but the thing with a project is if it’s already have ready to use components in react :wink:

I recently tried the package - I had a bit of difficulty mapping how Electron works to how this is configured, but it only took me a few hours to figure it out and otherwise, I found the package to be excellent. It would be really great if it were extended to run the Meteor server and database with-in itself.

I was able to get everything working the way I would like, but I am stuck with one thing: does anyone know how to disable pinch to zoom, and how to override the Command + R for refresh

2 Likes

mate without seeming like am imposing, you should totally make a blog post about doing this with a demo meteor react app. would be very much appreciated.

Does anyone have experience distributing on the Mac App Store with this package?

We’re working on that right now and it seems like it’s gonna work. Will post back results.

1 Like

Sweet, please keep us posted on how it’s going. I have been delayed several months now by this issue.

There is a new branch with a commit that might fix an issue around the build tool, however, I’m not sure if its complete or runnable.

@adammoisa hey man have you had any luck?

Hey @msavin, it took a while, a lot of luck, and more importantly, a lot of persistence from my relentless teammate @oleksiitrukhanov (who gets 100% of the credit and is the expert on the matter). Happy to say that Travis is successfully building a macOS app with meteor-desktop and uploading it to Appstoreconnect!


Our project goals were for Travis to perform the following actions on a push to master (*B&D = build and deploy):

  • iOS: B&D an iOS app to App Store (Appstoreconnect) :white_check_mark:
    • And automatically push the new build out to internal testflight users
  • Android: B&D an Android app to Google Play :white_check_mark:
    • And auto publish to alpha or beta testing channels
  • Windows: B&D a Windows Electron app (using meteor-desktop):
    • And deploy it to the Windows Store :hourglass_flowing_sand:
      • @oleksiitrukhanov is still going strong and I’m confident he’ll get this one too :slight_smile:!
    • As a .exe to an S3 bucket :white_check_mark:
      • We save the previous version to a versions folder and write the new .exe to the same location the old version was. This way, we never have to update download links (it always points to the same spot which always has the latest version)
  • Mac: B&D a macOS Electron app (using meteor-desktop):
    • And deploy it to the Mac App store (Appstoreconnect) :white_check_mark:
    • As a .app to an S3 bucket :white_check_mark:
      • Storage logic same as Windows app
  • Web: B&D the web app to 2 servers behind a load balancer :white_check_mark:
    • Without wasting build time of course, which was a challenge: when PM2 is configured to deploy to multiple servers, it builds & deploys for each server listed, which means you are waiting for a full build for each server! @oleksiitrukhanov successfully configured the process to build once and reuse the same bundle to deploy to multiple servers. The implications are fantastic - scaling and deploying to multiple servers is now only limited by the actual upload-bundle-to-server time, and not also limited by the build time
      • Fast: totalTime = buildTime + (servers.length * uploadTime), vs.
      • Slow: totalTime = servers.length * (buildTime + uploadTime)

We also have a separate ‘server-only’ meteor app that services many client requests (especially reqs to other external services, and another that runs lengthy cron jobs) to which a client (or method) connects over DDP (2 servers behind a LB), but this is a separate repo so it is a totally separate process. We reuse the build once -> deploy to multiple logic in Travis which makes deploying super efficient.

We’d like to eventually create an AMI on deploy and then spawn up instances from the AMI and add any spawned instances to the LB, which would then allow us to both decide on the # of servers at deploy and to scale up post-deploy in realtime, without first configing new servers then redeploying, etc. Ready for another challenge, @oleksiitrukhanov? :smiley:


One important feature we wanted is for each of these main processes to be created and run as separate jobs on Travis instead of one long process on a push to master. Concurrent jobs are great (although more than 1 concurrent build on Travis += $$$ [$69/mo for 1 concurrent, $129/mo for 2, $249/mo for 5]).

Nevertheless, even if you have to wait for each to run one after the other, it’s much easier to debug if (when) one fails (and it takes ~the same amount of time as one long thread would have in any case).


It was beyond a lot of work and persistence and again, I’d like to thank and praise @oleksiitrukhanov for not giving up for weeks and for getting this done. He’ll be publishing an in-depth post on Medium shortly, specifically describing the macOS process to which I’ll provide a link here when complete. It will cover all the provisioning/configs and external services used to successfully deploy the app to Appstoreconnect.

He’ll also be writing an in-depth article covering all of the above (iOS, Android, Windows (.exe and Store), Mac (.app and Store), and Web (multiple servers/load balancing)) for anyone who’s looking for similar functionality but want’s to keep (most of) their hair on their head :slight_smile:. Will post it here as well when complete.

We’d like to help anyone who’s struggling with this so if you or others have questions, don’t hesitate to post! Probably best to wait on the article for initial guidance, but nevertheless we’re here to help if we can.


Side note, we are also using the https://github.com/cult-of-coders/redis-oplog package which kicks butt, especially considering the extreme targeting available using channel: 'threads::' + threadId + '::messages' for publications. On our db of 6M+ docs that are constantly churning, performance is :ok_hand:. And we love the ability to publish updates to redis with total control or from external services via vent and the handy documentation on outside mutations. Big thanks to @diaconutheodor on the fantastic package!

5 Likes

Haha! Niceee! Maybe you could publish some performance specs? I would like to see how it performs on large-scale! The community and I built it exactly for needs like this!

Love the fact you put Vent to good use. Thanks for the appreciation!

2 Likes

We definitely will, as soon as we find time between building new features + fixing bugs :upside_down_face:. But in all seriousness, we will do some tests and post some specs and I’ll share the details in the redis-oplog thread. Needless to say, it’s made the difference between the app not working and being lightning fast. :clap::clap:

1 Like

Amazing news! If it helps, I am happy to jump on a screenshare call to try your method, and I can help document the steps along the way. Otherwise, looking forward to seeing the write-up!

@adammoisa @oleksiitrukhanov hey folks, any news on how you got the mas build to work? perhaps you can share the configuration file? I’m like half a year behind on getting this launched, so I’m counting on ya :smiley:

Hey, @msavin. Sorry for the late answer. I will write a small manual about how to build the installer with the meteor-desktop.

  1. Create an Apple developer account on developer.apple.com. The Apple Developer Program annual fee is 99 USD and the Apple Developer Enterprise Program annual fee is 299 USD.

  2. Create a desktop project in the apple store connect appstoreconnect.apple.com

  3. Create certificates and provisioning profiles. You could do this on xcode or on your apple developer account.

  1. Mac App Distribution
  2. Mac Installer Distribution
  3. Developer ID Application
  4. Developer ID Installer
  5. Mac Development
    Export them into a single p12 file and export the path to this p12 file in the "CSC_LINK" env var.
  1. Create a distribution embedded.provisionprofile for macOS on your apple developer account. And put in the root of your project.
  • you can easy find the info about certifications and provisioning profiles in the internet.
  1. Generate app-specific password. This is how to do it https://www.imore.com/how-generate-app-specific-passwords-iphone-ipad-mac

  2. Create a special icns icon for your app. This is how to do it https://www.youtube.com/watch?v=3TPtHX9O3IU

  3. Create an entitlements.mas.plist file in the root of the project or somewhere else inside of it.

  • YOUR_APPLE_TEAM_ID - it could be yours apple id if you doesn’t work in a team.
  • YOUR.BUNDLE.ID - you can get it from the created app in the apple store connect in an ‘App information’ tab
<?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/>
<key>com.apple.security.application-groups</key>
<string>YOUR_APPLE_TEAM_ID.YOUR.BUNDLE.ID</string>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
  1. Meteor-Desktop settings:
{
  "name": "TYPE_HERE_YOUR_PROJECT'S_NAME",
  "version": "0.0.1",
  "projectName": "TYPE_HERE_YOUR_PROJECT'S_NAME_BETTER_WITHOUT_SPACES",
  "devTools": true,
  "devtron": true,
  "desktopHCP": true,
  "desktopHCPIgnoreCompatibilityVersion": false,
  "autoUpdateFeedUrl": "http://127.0.0.1/update/:platform/:version",
  "autoUpdateFeedHeaders": {},
  "autoUpdateCheckOnStart": true,
  "webAppStartupTimeout": 60000,
  "exposeLocalFilesystem": false,
  "window": {
      "icon": "@assets/meteor.png",
      "_windows": {
          "icon": "@assets/meteor.ico"
      }
  },
  "windowDev": {
  },
  "uglify": true,
  "plugins": {
      "meteor-desktop-splash-screen": {
          "version": "0.4.3",
          "windowSettings": {
              "width": 800,
              "height": 600
          },
          "updateScreen": true,
          "updateScreenSettings": {
              "imagePath": "updating.gif",
              "windowSettings": {
                  "width": 400,
                  "height": 300
              },
              "clickThrough": false,
              "style": {
                  "background-size": "auto"
              }
          }
      }
  },
  "dependencies": {},
  "packageJsonFields": {
      "description": "YOUR_PROJECT_DESCRIPTION",
      "author": "AUTHOR",
      "private": true
  },
  "builderOptions": {
      "icon": ".desktop/assets/meteor.ico",
      "appId": "YOUR.BUNDLE.ID",
      "productName": "PROJECT'S_NAME",
      "artifactName": "${productName}.${ext}",
      "compression": "maximum",
      "win": {
          "target": ["nsis"]
      },
      "squirrelWindows": {
          "iconUrl": "https://github.com/wojtkowiak/meteor-desktop/blob/beta/scaffold/assets/meteor.ico?raw=true",
          "loadingGif": ".desktop/assets/loading.gif"
      },
      "publish": [
          {
              "provider": "generic",
              "url": "http://127.0.0.1:8080/"
          }
      ],
      "mac": {
          "type": "distribution",
          "icon": "PATH_TO_YOUR icns ICON_FILE",
          "extendInfo": {
             "ElectronTeamID": "APPLE_TEAM_ID",
             "com.apple.developer.team-identifier": "APPLE_TEAM_ID",
             "com.apple.application-identifier": "APPLE_TEAM_ID.YOUR.BUNDLE.ID"
           },
           "entitlements": "PATH_TO entitlements.mas.plist",
           "provisioningProfile": "embedded.provisionprofile",
           "target": ["mas"],
           "bundleVersion": "1"
      },
      "dmg": {
          "icon": "PATH_TO_YOUR icns ICON_FILE",
          "iconSize": 54,
          "contents": [
              { "x": 377, "y": 190, "type": "link", "path": "/Applications" },
              { "x": 63, "y": 190, "type": "file" }
          ],
          "window": {
              "width": 480,
              "height": 300
          }
      }
  },
  "packagerOptions": {
      "icon": ".desktop/assets/meteor.ico",
      "_osx": {
          "icon": "PATH_TO_YOUR icns ICON_FILE"
      },
      "appVersion": "@version",
      "buildVersion": "1",
      "appCopyright": "COPYRIGHT_TEXT",
      "win32metadata": {
         "CompanyName": "COMPANY_NAME",
             "FileDescription": "FILE_DESCRIPTION",
             "OriginalFilename": "PRODUCT_NAME.exe",
             "ProductName": "PRODUCT_NAME",
             "InternalName": "PRODUCT_NAME.exe"
      }
  }
 }
  1. Install mobile platforms for meteor (iOS or Android)
  • if you didn’t run the mobile platform before run the ‘meteor rebuild’ - it would install the necessary cordova and meteor packages.
  1. Then run command:
npm run desktop -- build-installer https://YOUR_SERVER_ADRESS --mac  --build-meteor --meteor-settings settings.json

if you don’t use meteor settings - just remove it from the command.

  1. If all is OK. You will have a folder with the desktop installer archive file. If you use the xcode 10 or higher upload the app package file with the command:
xcrun altool --upload-app -f "PATH_TO_YOUR pkg FILE" -u YOUR_APPLE_ID -p APPLE_APPLICATION_SPECIFIC_PASSWORD

if the xcode is lower than 10 version it might not found the altool with xcrun so run at once:

ln -s "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool" /usr/local/bin/altool
ln -s "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms" /usr/local/bin/itms #itms is needed, otherwise altool will not work correctly

then run the same without xcrun

altool --upload-app -f "PATH_TO_YOUR pkg FILE" -u YOUR_APPLE_ID -p APPLE_APPLICATION_SPECIFIC_PASSWORD

It should upload it to the apple store connect.

One main thing is to secure your p12 certificate file, provisioning profile, apple_app_specific_password, plist file. Add it to gitignore file if you use github.

That’s it!
The most of the settings for macOS build I took form electron-build docs

https://www.electron.build/configuration/configuration
9 Likes

How can I build the ICNS file to submit to Mac store??

This is so cool. Great work! :grin:

@oleksiitrukhanov thanks for writing that up, it looks like we had the wrong configuration for the mac build, and now that it’s set, things are looking much better.

However, I’m running into this issue, seems common with electron-builder, and while I tried the proposed solutions, I could not get it working.

DEBUG  electronBuilder:  calling build from electron-builder
  • electron-builder version=20.28.4
  • writing effective config file=.desktop-installer/builder-effective-config.yaml
DEBUG  electronBuilder:  moving node_modules out, because we have them already in app.asar
mv /Users/maxDocuments/GitHub/color-app/.meteor/desktop-build/node_modules /Users/maxDocuments/GitHub/color-app/.meteor/.desktop_node_modules
  • packaging       platform=mas arch=x64 electron=2.0.10 appOutDir=.desktop-installer/mas
  ⨯ remove /Users/maxDocuments/GitHub/color-app/.desktop-installer/mas/color.app/Contents/_CodeSignature/CodeResources: permission denied
github.com/develar/app-builder/vendor/github.com/develar/go-fs-util.EnsureEmptyDir
	/Users/develar/go/src/github.com/develar/app-builder/vendor/github.com/develar/go-fs-util/fs.go:101
github.com/develar/app-builder/pkg/electron.unpackElectron.func1.1
	/Users/develar/go/src/github.com/develar/app-builder/pkg/electron/electronUnpack.go:39
github.com/develar/app-builder/pkg/util.MapAsyncConcurrency.func2
	/Users/develar/go/src/github.com/develar/app-builder/pkg/util/async.go:67
runtime.goexit
	/usr/local/Cellar/go/1.10.3/libexec/src/runtime/asm_amd64.s:2361

ERROR  electronBuilder:  error while building installer:  Error: /Users/maxDocuments/GitHub/color-app/node_modules/app-builder-bin/maxapp-builder exited with code 1
    at ChildProcess.childProcess.once.code (/Users/maxDocuments/GitHub/color-app/node_modules/builder-util/src/util.ts:254:14)
    at Object.onceWrapper (events.js:277:13)
    at ChildProcess.emit (events.js:189:13)
    at maybeClose (internal/child_process.js:970:16)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:259:5)
From previous event:
    at unpack (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/out/electron/ElectronFramework.js:191:18)
    at Object.prepareApplicationStageDirectory (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/electron/ElectronFramework.ts:148:50)
    at /Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/platformPackager.ts:179:21
    at Generator.next (<anonymous>)
From previous event:
    at MacPackager.doPack (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/platformPackager.ts:166:165)
    at /Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/macPackager.ts:108:20
    at Generator.next (<anonymous>)
From previous event:
    at MacPackager.pack (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/macPackager.ts:80:95)
    at /Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/packager.ts:376:24
    at Generator.next (<anonymous>)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)
From previous event:
    at Packager.doBuild (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/packager.ts:344:39)
    at /Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/packager.ts:314:57
    at Generator.next (<anonymous>)
    at /Users/maxDocuments/GitHub/color-app/node_modules/graceful-fs/graceful-fs.js:99:16
    at /Users/maxDocuments/GitHub/color-app/node_modules/graceful-fs/graceful-fs.js:43:10
    at FSReqWrap.args [as oncomplete] (fs.js:140:20)
From previous event:
    at Packager._build (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/packager.ts:285:133)
    at /Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/packager.ts:281:23
    at Generator.next (<anonymous>)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)
From previous event:
    at Packager.build (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/packager.ts:238:14)
    at build (/Users/maxDocuments/GitHub/color-app/node_modules/app-builder-lib/src/index.ts:58:28)
    at Object.build (/Users/maxDocuments/GitHub/color-app/node_modules/electron-builder/src/builder.ts:227:10)
    at InstallerBuilder.build (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/lib/electronBuilder.js:334:43)
    at tryCatch (/Users/maxDocuments/GitHub/color-app/node_modules/regenerator-runtime/runtime.js:62:40)
    at Generator.invoke [as _invoke] (/Users/maxDocuments/GitHub/color-app/node_modules/regenerator-runtime/runtime.js:288:22)
    at Generator.prototype.(anonymous function) [as next] (/Users/maxDocuments/GitHub/color-app/node_modules/regenerator-runtime/runtime.js:114:21)
    at step (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/electronBuilder.js:26:221)
    at _next (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/electronBuilder.js:26:409)
    at /Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/electronBuilder.js:26:477
    at new Promise (<anonymous>)
    at InstallerBuilder.<anonymous> (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/electronBuilder.js:26:97)
    at InstallerBuilder.build (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/electronBuilder.js:476:23)
    at MeteorDesktop.build (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/lib/index.js:117:40)
    at tryCatch (/Users/maxDocuments/GitHub/color-app/node_modules/regenerator-runtime/runtime.js:62:40)
    at Generator.invoke [as _invoke] (/Users/maxDocuments/GitHub/color-app/node_modules/regenerator-runtime/runtime.js:288:22)
    at Generator.prototype.(anonymous function) [as next] (/Users/maxDocuments/GitHub/color-app/node_modules/regenerator-runtime/runtime.js:114:21)
    at step (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/index.js:36:221)
    at _next (/Users/maxDocuments/GitHub/color-app/node_modules/meteor-desktop/dist/index.js:36:409)
Macs-MacBook-Air:color-app mac$ 

My settings.json was based on what you had pasted, though a little trimmed down as I’ve tried to debug this problem.

  "name": "Color",
  "version": "1.0.0",
  "projectName": "Color",
  "devTools": true,
  "devtron": true,
  "desktopHCP": true,
  "desktopHCPIgnoreCompatibilityVersion": false,
  "autoUpdateFeedUrl": "http://127.0.0.1/update/:platform/:version",
  "autoUpdateFeedHeaders": {},
  "autoUpdateCheckOnStart": true,
  "webAppStartupTimeout": 60000,
  "exposeLocalFilesystem": false,
  "exposedModules": ["webFrame"],
  "window": {
      "icon": "@assets/color.png",
      "_windows": {
          "icon": "@assets/meteor.ico"
      },
      "width": 1062,
      "height": 622,
      "frame": false
  },
  "windowDev": {
  },
  "uglify": true,
  "plugins": {
  },
  "dependencies": {},
  "packageJsonFields": {
      "description": "Your New Color Scheme App",
      "author": "Corp.",
      "private": true
  },
  "builderOptions": {
      "icon": ".desktop/assets/meteor.ico",
      "appId": "ooo.corp.color",
      "productName": "Color",
      "artifactName": "${productName}.${ext}",
      "compression": "maximum",
      "publish": [
          {
              "provider": "generic",
              "url": "http://127.0.0.1:8080/"
          }
      ],
      "mac": {
          "type": "distribution",
          "icon": "build/logo.icns",
          "extendInfo": {
             "ElectronTeamID": "5584CR723U",
             "com.apple.developer.team-identifier": "5584CR723U",
             "com.apple.application-identifier": "ooo.corp.color"
           },
           "entitlements": "build/entitlements.mas.plist",
           "provisioningProfile": "build/embedded.provisionprofile",
           "target": ["mas"],
           "bundleVersion": "1"
      }
  },
  "packagerOptions": {
      "icon": ".desktop/assets/meteor.ico",
      "_osx": {
          "icon": "build/logo.icns"
      },
      "appVersion": "@version",
      "buildVersion": "1",
      "appCopyright": "Copyright 2018 Corp. All rights reserved.",
      "win32metadata": {
         "CompanyName": "COMPANY_NAME",
             "FileDescription": "FILE_DESCRIPTION",
             "OriginalFilename": "PRODUCT_NAME.exe",
             "ProductName": "PRODUCT_NAME",
             "InternalName": "PRODUCT_NAME.exe"
      }
  }
}

I’d been sitting on this thing for almost a year now :joy::sob: would love to get it off my to-do list. If you or someone else thinks it could be worth it jump on Skype, I would be happy to pay for the time.