├── .gitignore ├── README.md ├── src ├── assets │ ├── dock │ │ ├── dock-icon.icns │ │ └── dock-icon.png │ ├── onboarding │ │ ├── SF_Light.ttf │ │ ├── SF_Medium.ttf │ │ ├── SF_Regular.ttf │ │ ├── example-menubar.png │ │ ├── example.png │ │ ├── icon.png │ │ └── logo.png │ ├── tailwind.min.css │ ├── tray │ │ ├── CopyTemplate.png │ │ ├── IconTemplate.png │ │ └── IconTemplate@2x.png │ └── vue.js ├── binaries │ ├── setapp-nodejs-wrapper.node │ └── setapp-nodejs-wrapper │ │ ├── README.md │ │ ├── bin │ │ ├── darwin-arm64-85 │ │ │ └── setapp-nodejs-wrapper.node │ │ └── darwin-x64-85 │ │ │ └── setapp-nodejs-wrapper.node │ │ ├── binding.gyp │ │ ├── build │ │ ├── Makefile │ │ ├── Release │ │ │ ├── .deps │ │ │ │ └── Release │ │ │ │ │ ├── obj.target │ │ │ │ │ └── setapp │ │ │ │ │ │ └── setapp-lib-mapping.o.d │ │ │ │ │ └── setapp.node.d │ │ │ ├── .forge-meta │ │ │ ├── obj.target │ │ │ │ └── setapp │ │ │ │ │ └── setapp-lib-mapping.o │ │ │ └── setapp.node │ │ ├── binding.Makefile │ │ ├── config.gypi │ │ ├── gyp-mac-tool │ │ └── setapp.target.mk │ │ ├── integrate_setapp.sh │ │ ├── libSetapp │ │ ├── Resources │ │ │ ├── Base.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── de.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── en.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── es.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── fr.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── it.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── ja.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── ko.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── nl.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── pl.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── pt-BR.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── ru.lproj │ │ │ │ └── Setapp.strings │ │ │ ├── uk.lproj │ │ │ │ └── Setapp.strings │ │ │ └── zh-Hans.lproj │ │ │ │ └── Setapp.strings │ │ ├── Setapp.h │ │ └── libSetapp.a │ │ ├── package.json │ │ └── setapp-lib-mapping.mm ├── build │ ├── build-setapp-legacy.sh │ ├── build-setapp.sh │ ├── build-universal.js │ ├── build-universal.sh │ ├── electron.arm64.config.js │ ├── electron.config.js │ ├── electron.x64.config.js │ ├── entitlements.mac.plist │ ├── notarize.js │ ├── setapp-notarize.js │ └── universal │ │ ├── .prettierrc.json │ │ ├── .releaserc.json │ │ ├── README.md │ │ ├── dist │ │ ├── cjs │ │ │ ├── asar-utils.d.ts │ │ │ ├── asar-utils.js │ │ │ ├── asar-utils.js.map │ │ │ ├── debug.d.ts │ │ │ ├── debug.js │ │ │ ├── debug.js.map │ │ │ ├── file-utils.d.ts │ │ │ ├── file-utils.js │ │ │ ├── file-utils.js.map │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── index.js.map │ │ │ ├── sha.d.ts │ │ │ ├── sha.js │ │ │ └── sha.js.map │ │ └── esm │ │ │ ├── asar-utils.d.ts │ │ │ ├── asar-utils.js │ │ │ ├── asar-utils.js.map │ │ │ ├── debug.d.ts │ │ │ ├── debug.js │ │ │ ├── debug.js.map │ │ │ ├── file-utils.d.ts │ │ │ ├── file-utils.js │ │ │ ├── file-utils.js.map │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── index.js.map │ │ │ ├── sha.d.ts │ │ │ ├── sha.js │ │ │ └── sha.js.map │ │ ├── entry-asar │ │ ├── has-asar.js │ │ └── no-asar.js │ │ ├── package.json │ │ ├── src │ │ ├── asar-utils.ts │ │ ├── debug.ts │ │ ├── file-utils.ts │ │ ├── index.ts │ │ └── sha.ts │ │ ├── tsconfig.esm.json │ │ ├── tsconfig.json │ │ └── yarn.lock ├── config.js ├── index.js ├── libs │ ├── imessage │ │ ├── index.js │ │ ├── lib │ │ │ └── messages-db.js │ │ └── macos_versions.json │ └── parse-otp-message │ │ ├── index.js │ │ ├── lib │ │ ├── auth-words.js │ │ ├── custom-filters.js │ │ ├── interventions.js │ │ ├── known-services.js │ │ ├── service-patterns.js │ │ └── stopwords.js │ │ └── test │ │ ├── custom.js │ │ └── index.js ├── onboarding.html ├── overlay.html ├── package.json ├── preload.js └── setapp.js └── webextension-example ├── background.js ├── content.js ├── icon128.png ├── icon16.png ├── icon48.png ├── manifest.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | dist_electron 4 | dist-electron 5 | .env 6 | .DS_Store 7 | developeridapplication.p12 8 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Ohtipi 3 | 4 | iMessage OTP AutoFill in any browser on macOS 5 | 6 | ### How's it work? 7 | 8 | OhTipi relies on a local macOS app that looks for incoming texts with OTP codes in them. The macOS app copies those codes to your clipboard along with a notification. Everything happens locally and nothing is ever sent to a server. 9 | 10 | [![OpenGraph Image.](https://sofriendly.s3.amazonaws.com/ohtipiopengraph.png "OpenGraph")](https://ohtipi.com/) 11 | 12 | Download for macOS at [Ohtipi.com](https://ohtipi.com/) 13 | 14 | *** 15 | 16 | ## Run for development 17 | 18 | ```shell 19 | npm run install; 20 | npm run dev; 21 | ``` 22 | 23 | 💡 Note: During development, Full Disk Access permission must be granted to whichever app is running the `npm run dev` command (usually `Terminal.app` or `Visual Studio Code.app`). [More info...](https://github.com/Yac-Team/ohtipi/issues/6) 24 | 25 | ## Find the important bits 26 | 27 | * [Main process](https://github.com/Yac-Team/ohtipi/blob/main/src/index.js) 28 | * [iMessage service](https://github.com/Yac-Team/ohtipi/tree/main/src/libs/imessage) which queries the local `sqlite` database 29 | * [Modified fork](https://github.com/Yac-Team/ohtipi/tree/main/src/libs/parse-otp-message) of [`parse-otp-message`](https://github.com/transitive-bullshit/parse-otp-message), includes [service list](https://github.com/Yac-Team/ohtipi/blob/main/src/libs/parse-otp-message/lib/known-services.js), [service patterns](https://github.com/Yac-Team/ohtipi/blob/main/src/libs/parse-otp-message/lib/service-patterns.js) and [auth words](https://github.com/Yac-Team/ohtipi/blob/main/src/libs/parse-otp-message/lib/auth-words.js). Particuarly difficult cases can otherwise be caught and handled within [custom filters](https://github.com/Yac-Team/ohtipi/blob/main/src/libs/parse-otp-message/lib/custom-filters.js) 30 | 31 | ## Release flow 32 | 33 | The following will only function with proprietary signing keys, etc. It is for internal use only. 34 | 35 | ### Standard release 36 | 37 | 38 | ```shell 39 | npm run install; 40 | npm run release; 41 | ``` 42 | 43 | ### Setapp release 44 | 45 | Open `config.js` and set `{ build.setApp }` to `true`. 46 | 47 | Make sure all `.node` native modules are compiled against the correct version of Node, Electron, etc. This project currently uses `sqlite3`, `node-mac-permissions` and `setapp-nodejs-wrapper`. The build script can be seen in `./build/build-setapp.sh`. `lipo` is used to cross-compile `.node` modules for both `arm64` and `x64`. 48 | 49 | ```shell 50 | npm run build:setapp; 51 | ``` 52 | 53 | Distribution can now be found in `/dist/mac-universal`. 54 | 55 | 💡 Note: You may need to re-codesign the Universal app bundle: 56 | 57 | ```shell 58 | codesign -fv --deep -s CBA[..................] ./dist/mac-universal/Ohtipi.app 59 | ``` 60 | -------------------------------------------------------------------------------- /src/assets/dock/dock-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/dock/dock-icon.icns -------------------------------------------------------------------------------- /src/assets/dock/dock-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/dock/dock-icon.png -------------------------------------------------------------------------------- /src/assets/onboarding/SF_Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/SF_Light.ttf -------------------------------------------------------------------------------- /src/assets/onboarding/SF_Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/SF_Medium.ttf -------------------------------------------------------------------------------- /src/assets/onboarding/SF_Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/SF_Regular.ttf -------------------------------------------------------------------------------- /src/assets/onboarding/example-menubar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/example-menubar.png -------------------------------------------------------------------------------- /src/assets/onboarding/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/example.png -------------------------------------------------------------------------------- /src/assets/onboarding/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/icon.png -------------------------------------------------------------------------------- /src/assets/onboarding/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/onboarding/logo.png -------------------------------------------------------------------------------- /src/assets/tray/CopyTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/tray/CopyTemplate.png -------------------------------------------------------------------------------- /src/assets/tray/IconTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/tray/IconTemplate.png -------------------------------------------------------------------------------- /src/assets/tray/IconTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/assets/tray/IconTemplate@2x.png -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/binaries/setapp-nodejs-wrapper.node -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/README.md: -------------------------------------------------------------------------------- 1 | # Setapp lib Node.JS plugin 2 | 3 | The only development dependency of this project is [Node.js](https://nodejs.org), so make sure you have it installed. 4 | 5 | ## Prerequisites 6 | 7 | - Download Setapp Library archive at the top of the Add New Version/Edit Version page (`libSetapp.zip`) in [vendor account](https://developer.setapp.com). 8 | - Unzip this archive to the root of this project to folder `libSetapp`. 9 | 10 | ## Build process 11 | 12 | 1. Initial dependencies installation: 13 | 14 | ```sh 15 | npm install 16 | ``` 17 | 18 | 2. Build `libSetapp` nodejs wrapper: 19 | 20 | ```sh 21 | npm run build 22 | ``` 23 | 24 | 3. Use `setapp.node` in your Electron app project([documentation](https://docs.setapp.com/docs/library-integration)): 25 | 26 | ```js 27 | const setapp = require(process.resourcesPath + "/setapp.node"); 28 | ``` 29 | 30 | 4. Run integration script in order to add `libSetapp` to your Electron app: 31 | 32 | ```sh 33 | ./integrate_setapp.sh 34 | ``` 35 | 36 | ``` 37 | params: 38 | app - path to your builded application .app 39 | sign - code sign identity to resign your updated build 40 | ``` 41 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/bin/darwin-arm64-85/setapp-nodejs-wrapper.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/binaries/setapp-nodejs-wrapper/bin/darwin-arm64-85/setapp-nodejs-wrapper.node -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/bin/darwin-x64-85/setapp-nodejs-wrapper.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/binaries/setapp-nodejs-wrapper/bin/darwin-x64-85/setapp-nodejs-wrapper.node -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "setapp", 5 | "sources": [ 6 | "setapp-lib-mapping.mm" 7 | ], 8 | "include_dirs": [ 9 | "" 4 | echo " [-sign [-entitlements ]]" 5 | } 6 | 7 | function check_result() 8 | { 9 | RESULT="$1" 10 | MESSAGE="$2" 11 | if [ $RESULT != 0 ]; then 12 | echo "$MESSAGE" 13 | exit $RESULT 14 | fi 15 | } 16 | 17 | # ---------------------------------------------- 18 | # check input params 19 | 20 | APP_PATH="" 21 | SIGN_IDENTITY="" 22 | ENTITLEMENTS="" 23 | while test $# -gt 0; do 24 | case "$1" in 25 | -h|--help) 26 | print_usage 27 | exit 0 28 | ;; 29 | -app) 30 | shift 31 | if test $# -gt 0; then 32 | APP_PATH="$1" 33 | fi 34 | shift 35 | ;; 36 | -sign) 37 | shift 38 | if test $# -gt 0; then 39 | SIGN_IDENTITY="$1" 40 | fi 41 | shift 42 | ;; 43 | -entitlements) 44 | shift 45 | if test $# -gt 0; then 46 | ENTITLEMENTS="$1" 47 | fi 48 | shift 49 | ;; 50 | *) 51 | echo "Unknown input parameter \"$1\"" 52 | exit 1 53 | ;; 54 | esac 55 | done 56 | 57 | if [ ! -d "$APP_PATH" ]; then 58 | echo "Path to application bundle is absent." 59 | exit 1 60 | fi 61 | 62 | # replace Setapp library resources 63 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 64 | LIB_DIR_PATH="$SCRIPT_DIR/libSetapp" 65 | if [ ! -d "$LIB_DIR_PATH" ]; then 66 | echo "libSetapp directory is absent" 67 | exit 1 68 | fi 69 | 70 | CUR_DIR="$PWD" 71 | cd "$SCRIPT_DIR" 72 | 73 | # build setapp.node 74 | npm install && npm run build 75 | check_result $? 76 | cd "$CUR_DIR" 77 | 78 | # copy node to application bundle 79 | SETAPP_NODE="$SCRIPT_DIR/build/Release/setapp.node" 80 | APP_RESOURCES_DIR="$APP_PATH/Contents/Resources" 81 | cp -p "$SETAPP_NODE" "$APP_RESOURCES_DIR" 82 | check_result $? 83 | 84 | # copy localized resources to application bundle 85 | LIB_RESOURCES_DIR="$SCRIPT_DIR/libSetapp/Resources" 86 | LOCALIZED_FILES=$(find "$LIB_RESOURCES_DIR" -name "*.strings") 87 | IFS=$'\n\b' 88 | for FILE in $LOCALIZED_FILES; do 89 | DIR_NAME="$(basename $(dirname $FILE))" 90 | if [ -d "$APP_RESOURCES_DIR/$DIR_NAME" ]; then 91 | cp -p "$FILE" "$APP_RESOURCES_DIR/$DIR_NAME" 92 | check_result $? 93 | fi 94 | done 95 | 96 | # codesign if needed 97 | if [ -n "$SIGN_IDENTITY" ]; then 98 | if [ -n "$ENTITLEMENTS" ]; then 99 | codesign -fv -s "$SIGN_IDENTITY" --entitlements "$ENTITLEMENTS" "$APP_PATH" 100 | check_result $? 101 | else 102 | codesign -fv -s "$SIGN_IDENTITY" "$APP_PATH" 103 | check_result $? 104 | fi 105 | fi 106 | 107 | echo "Now your app is ready to be submitted to Setapp ;)" 108 | exit 0 109 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/Base.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "Welcome to %@ %@"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "Let’s stay in touch!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@ invites you to receive its newsletters and offers."; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp will share your %@ email with the developers of %@."; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "Sure, I’m In"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "No, Thanks"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "This copy of %@ requires an active Setapp account installed on your Mac."; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Download and install Setapp to use %@ and discover even more awesome applications."; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "Learn More…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "Quit"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/de.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Email sharing dialog description format */ 2 | "%@ invites you to receive its newsletters and offers." = "%@ lädt dich ein, Newsletter und Angebote zu erhalten."; 3 | 4 | /* Alert informative text */ 5 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Lade und installiere Setapp, um %@ zu nutzen und noch mehr tolle Anwendungen zu entdecken."; 6 | 7 | /* Button caption */ 8 | "Learn More…" = "Mehr erfahren …"; 9 | 10 | /* Email sharing dialog title format */ 11 | "Let’s stay in touch!" = "Bleiben wir doch in Kontakt!"; 12 | 13 | /* Email sharing dialog forbid button title */ 14 | "No, Thanks" = "Nein danke"; 15 | 16 | /* Button caption */ 17 | "Quit" = "Beenden"; 18 | 19 | /* Email sharing dialog additional info format */ 20 | "Setapp will share your %@ email with the developers of %@." = "Setapp gibt deine E-Mail-Adresse %@ an den Entwickler von %@ weiter."; 21 | 22 | /* Email sharing dialog allow button title */ 23 | "Sure, I’m In" = "Klar, bin dabei"; 24 | 25 | /* Alert message text */ 26 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Diese Version von %@ erfordert, dass ein aktiver Setapp-Account auf deinem Mac installiert ist."; 27 | 28 | /* Release notes window title format */ 29 | "Welcome to %@ %@" = "Willkommen bei %@ %@"; 30 | 31 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/en.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "Welcome to %@ %@"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "Let’s stay in touch!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@ invites you to receive its newsletters and offers."; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp will share your %@ email with the developers of %@."; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "Sure, I’m In"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "No, Thanks"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "This copy of %@ requires an active Setapp account installed on your Mac."; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Download and install Setapp to use %@ and discover even more awesome applications."; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "Learn More…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "Quit"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/es.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Email sharing dialog description format */ 2 | "%@ invites you to receive its newsletters and offers." = "%@ te invita a recibir sus newsletters y ofertas."; 3 | 4 | /* Alert informative text */ 5 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Descarga e instala Setapp para usar %@ y descubrir todavía más aplicaciones increíbles."; 6 | 7 | /* Button caption */ 8 | "Learn More…" = "Más información..."; 9 | 10 | /* Email sharing dialog title format */ 11 | "Let’s stay in touch!" = "¡No perdamos el contacto!"; 12 | 13 | /* Email sharing dialog forbid button title */ 14 | "No, Thanks" = "No, gracias"; 15 | 16 | /* Button caption */ 17 | "Quit" = "Salir"; 18 | 19 | /* Email sharing dialog additional info format */ 20 | "Setapp will share your %@ email with the developers of %@." = "Setapp compartirá tu dirección de correo electrónico %@ con los desarrolladores de %@."; 21 | 22 | /* Email sharing dialog allow button title */ 23 | "Sure, I’m In" = "Sí, por supuesto"; 24 | 25 | /* Alert message text */ 26 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Esta copia de %@ requiere una cuenta activa de Setapp instalada en tu Mac."; 27 | 28 | /* Release notes window title format */ 29 | "Welcome to %@ %@" = "Bienvenido a %@ %@"; 30 | 31 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/fr.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Email sharing dialog description format */ 2 | "%@ invites you to receive its newsletters and offers." = "%@ vous invite à vous abonner à ses newsletters et à ses offres spéciales."; 3 | 4 | /* Alert informative text */ 5 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Téléchargez et installez Setapp pour utiliser %@ et découvrir des applications encore plus incroyables."; 6 | 7 | /* Button caption */ 8 | "Learn More…" = "En savoir plus…"; 9 | 10 | /* Email sharing dialog title format */ 11 | "Let’s stay in touch!" = "Gardons le contact !"; 12 | 13 | /* Email sharing dialog forbid button title */ 14 | "No, Thanks" = "Non merci"; 15 | 16 | /* Button caption */ 17 | "Quit" = "Fermer"; 18 | 19 | /* Email sharing dialog additional info format */ 20 | "Setapp will share your %@ email with the developers of %@." = "Setapp partagera votre adresse %@ avec les développeurs de %@."; 21 | 22 | /* Email sharing dialog allow button title */ 23 | "Sure, I’m In" = "Super, j'accepte"; 24 | 25 | /* Alert message text */ 26 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Cette copie de %@ nécessite un compte Setapp actif installé sur votre Mac."; 27 | 28 | /* Release notes window title format */ 29 | "Welcome to %@ %@" = "Bienvenue dans %@ %@"; 30 | 31 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/it.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Email sharing dialog description format */ 2 | "%@ invites you to receive its newsletters and offers." = "Sei stato invitato da %@ a iscriverti alla newsletter e ricevere offerte."; 3 | 4 | /* Alert informative text */ 5 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Scarica e installa Setapp per usare %@ e scoprire ancora altre applicazioni eccezionali."; 6 | 7 | /* Button caption */ 8 | "Learn More…" = "Altre informazioni…"; 9 | 10 | /* Email sharing dialog title format */ 11 | "Let’s stay in touch!" = "Rimaniamo in contatto!"; 12 | 13 | /* Email sharing dialog forbid button title */ 14 | "No, Thanks" = "No, grazie"; 15 | 16 | /* Button caption */ 17 | "Quit" = "Esci"; 18 | 19 | /* Email sharing dialog additional info format */ 20 | "Setapp will share your %@ email with the developers of %@." = "Setapp condivide il tuo indirizzo e-mail %@ con gli sviluppatori di %@."; 21 | 22 | /* Email sharing dialog allow button title */ 23 | "Sure, I’m In" = "Certo, accetto"; 24 | 25 | /* Alert message text */ 26 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Questa copia di %@ richiede un account Setapp attivo installato sul tuo Mac."; 27 | 28 | /* Release notes window title format */ 29 | "Welcome to %@ %@" = "Benvenuto in %@ %@"; 30 | 31 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/ja.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "%@%@へようこそ"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "最新情報をどうぞ!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@からのニュースレターと特別オファーを受け取る。"; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setappはあなたのメールアドレス\"%1$@\"を%2$@の開発者と共有します。"; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "了解する"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "いいえ、結構です"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "%@のバージョンを使用するには、お使いのMacに現在有効なSetappアカウントがインストールされている必要があります。"; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "%@を使用するには、Setappをダウンロードしてインストールしてください。Setappでは他にもすばらしいアプリケーションをご用意しています。"; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "詳しい情報…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "終了"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/ko.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "%@ %@에 오신 것을 환영합니다"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "최신 소식을 받아보세요!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@의 뉴스레터 및 할인 소식을 받을 수 있도록 초대합니다."; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp은 사용자의 %@ 이메일 주소를 %@ 앱 개발자와 공유합니다."; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "좋습니다"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "괜찮습니다"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "이 복사본의 %@을(를) 사용하려면 Mac에 활성화된 Setapp 계정이 설치되어 있어야 합니다."; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "%@ 앱을 사용하려면 Setapp을 다운로드하고 설치하세요. 다양하고 유용한 응용 프로그램도 둘어보세요."; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "더 알아보기…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "종료"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/nl.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "Welkom bij %@ %@"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "Laten we in contact blijven!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@ wil je graag nieuwsbrieven en aanbiedingen sturen."; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp deelt je e-mailadres %@ met de ontwikkelaars van %@."; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "Natuurlijk"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "Nee, bedankt"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Voor dit exemplaar van %@ heb je een actief Setapp-account op je Mac nodig."; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Download en installeer Setapp om %@ te gebruiken en ontdek nog meer geweldige apps."; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "Meer info…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "Sluit"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/pl.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "Witamy w %@ %@"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "Zostańmy w kontakcie!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@ zaprasza do subskrybowania newsletterów i ofert."; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp podzieli się Twoim e-mailem %@ z deweloperami %@."; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "Tak, chcę"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "Nie, dziękuję"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Ta kopia %@ wymaga aktywnego konta Setapp na Twoim Mac."; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Pobierz i zainstaluj Setapp żeby korzystać z %@ i odkrywać więcej niesamowitych programów."; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "Więcej…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "Zakończ"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/pt-BR.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Email sharing dialog description format */ 2 | "%@ invites you to receive its newsletters and offers." = "Você está convidado para assinar o boletim e receber ofertas do %@."; 3 | 4 | /* Alert informative text */ 5 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Baixe e instale o Setapp para usar o %@ e descobrir muitos outros aplicativos incríveis."; 6 | 7 | /* Button caption */ 8 | "Learn More…" = "Saiba Mais…"; 9 | 10 | /* Email sharing dialog title format */ 11 | "Let’s stay in touch!" = "Vamos manter contato!"; 12 | 13 | /* Email sharing dialog forbid button title */ 14 | "No, Thanks" = "Não, Obrigado"; 15 | 16 | /* Button caption */ 17 | "Quit" = "Encerrar"; 18 | 19 | /* Email sharing dialog additional info format */ 20 | "Setapp will share your %@ email with the developers of %@." = "O Setapp compartilhará o e‑mail %@ com os desenvolvedores do %@."; 21 | 22 | /* Email sharing dialog allow button title */ 23 | "Sure, I’m In" = "Claro, Eu Quero"; 24 | 25 | /* Alert message text */ 26 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Esta cópia do %@ requer que uma conta do Setapp esteja ativada no Mac."; 27 | 28 | /* Release notes window title format */ 29 | "Welcome to %@ %@" = "Bem-vindo ao %@ %@"; 30 | 31 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/ru.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Email sharing dialog description format */ 2 | "%@ invites you to receive its newsletters and offers." = "%@ приглашает вас подписаться на новости и предложения."; 3 | 4 | /* Alert informative text */ 5 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Загрузите и установите Setapp чтобы пользоваться «%@» и открыть для себя еще больше программ."; 6 | 7 | /* Button caption */ 8 | "Learn More…" = "Подробнее…"; 9 | 10 | /* Email sharing dialog title format */ 11 | "Let’s stay in touch!" = "Давайте будем на связи!"; 12 | 13 | /* Email sharing dialog forbid button title */ 14 | "No, Thanks" = "Нет, спасибо"; 15 | 16 | /* Button caption */ 17 | "Quit" = "Завершить"; 18 | 19 | /* Email sharing dialog additional info format */ 20 | "Setapp will share your %@ email with the developers of %@." = "Setapp поделится вашим адресом email %@ с разработчиками %@."; 21 | 22 | /* Email sharing dialog allow button title */ 23 | "Sure, I’m In" = "Да, хочу!"; 24 | 25 | /* Alert message text */ 26 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Этой копии «%@» для работы необходима активная учетная запись Setapp."; 27 | 28 | /* Release notes window title format */ 29 | "Welcome to %@ %@" = "Приветствуем в %@ %@"; 30 | 31 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/uk.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "Вітаємо у %@ %@"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "Залишаймося на зв'язку!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@ запрошує вас до своєї розсилки новин."; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp поділиться вашою електронною адресою %@ з розробниками %@."; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "Я з вами"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "Ні, дякую"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "Ця копія %@ потребує активної підписки Setapp на вашому Mac."; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "Завантажте та встановіть Setapp, щоб користуватись %@ та іншими чудовими програмами."; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "Дізнатись більше…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "Завершити"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Resources/zh-Hans.lproj/Setapp.strings: -------------------------------------------------------------------------------- 1 | /* Release notes window title format */ 2 | "Welcome to %@ %@" = "欢迎使用 %@ %@"; 3 | 4 | /* Email sharing dialog title format */ 5 | "Let’s stay in touch!" = "保持联系!"; 6 | 7 | /* Email sharing dialog description format */ 8 | "%@ invites you to receive its newsletters and offers." = "%@ 邀请您接收时事通讯和最新优惠信息。"; 9 | 10 | /* Email sharing dialog additional info format */ 11 | "Setapp will share your %@ email with the developers of %@." = "Setapp 将您的 %@ 电子邮件与 %@ 的开发者共享。"; 12 | 13 | /* Email sharing dialog allow button title */ 14 | "Sure, I’m In" = "当然,我在"; 15 | 16 | /* Email sharing dialog forbid button title */ 17 | "No, Thanks" = "不,谢谢"; 18 | 19 | /* Alert message text */ 20 | "This copy of %@ requires an active Setapp account installed on your Mac." = "此 %@ 副本需要在您的 Mac 上安装有效的 Setapp 帐户。"; 21 | 22 | /* Alert informative text */ 23 | "Download and install Setapp to use %@ and discover even more awesome applications." = "下载并安装 Setapp 以使用 %@ 以及探索更多实用的应用程序。"; 24 | 25 | /* Button caption */ 26 | "Learn More…" = "了解更多…"; 27 | 28 | /* Button caption */ 29 | "Quit" = "关闭"; 30 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/Setapp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Setapp.h 3 | // Setapp 4 | // 5 | // Created on 7/15/2016. 6 | // Copyright © 2016 Setapp Ltd. All rights reserved. 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #if !__has_feature(nullability) 16 | #define _Nonnull 17 | #define _Nullable 18 | #define NS_ASSUME_NONNULL_BEGIN 19 | #define NS_ASSUME_NONNULL_END 20 | #endif 21 | 22 | NS_ASSUME_NONNULL_BEGIN 23 | 24 | #pragma mark - Library Version API 25 | 26 | /*! @brief Constant that shows library version 27 | */ 28 | static NSString * const SCLibraryVersion = @"1.6.6"; 29 | 30 | #pragma mark - Request authorization code 31 | 32 | /// Enumerates the available authorization scope values. 33 | typedef NSString *const SCVendorAuthScope NS_EXTENSIBLE_STRING_ENUM; 34 | 35 | /// Grants authorization to check if the current Setapp user has an active subscription and thus can access the application. 36 | FOUNDATION_EXTERN SCVendorAuthScope SCVendorAuthScopeApplicationAccess; 37 | 38 | /// Requests an authorization code for communication with Setapp backend server. 39 | /// The code is used to obtain the access & refresh tokens from the Vendor API. 40 | /// 41 | /// This function requires an Internet connection and will fail with a corresponding error if the device is offline. 42 | /// 43 | /// @param clientID A string ID generated for the app's client in the Setapp developer account. 44 | /// @param scope An array of case-sensitive strings that specify the scope of functionalities to be authorized for the app's client. 45 | /// See the @c SCVendorAuthScope enum for the full list of available values. 46 | /// @param completionHandler A block executed upon the request completion. 47 | /// The first parameter is an optional @c NSString containing the requested auth code, 48 | /// the second parameter is an optional @c NSError containing a possible error. 49 | FOUNDATION_EXTERN void SCRequestAuthorizationCode(NSString *clientID, 50 | NSArray *scope, 51 | void(^completionHandler)(NSString *_Nullable authorizationCode, 52 | NSError *_Nullable error)); 53 | 54 | #pragma mark - Release Notes API 55 | 56 | /*! @brief Shows a release notes window if the application is 57 | * launched for the first time after update. 58 | */ 59 | FOUNDATION_EXTERN void SCShowReleaseNotesWindowIfNeeded(void); 60 | 61 | 62 | /*! @brief Shows a window with release notes. 63 | */ 64 | FOUNDATION_EXTERN void SCShowReleaseNotesWindow(void); 65 | 66 | 67 | /*! @brief Checks if a release notes window can be shown. 68 | * @deprecated This method is deprecated and always returns YES. 69 | */ 70 | FOUNDATION_EXTERN BOOL SCCanShowReleaseNotesWindow(void) DEPRECATED_ATTRIBUTE; 71 | 72 | 73 | #pragma mark - Usage Events API 74 | 75 | /*! @brief Reports special application events that denote app usage. 76 | * @discussion More information about special Setapp events is available in 77 | * the knowledge base. 78 | * @discussion Events must be reported only after the @c applicationDidFinishLaunching 79 | * method is called (if applicable). 80 | * @param eventName Setapp event names are described in the knowledge base. 81 | * @param eventInfo Additional info about an event. Currently, not supported. 82 | */ 83 | FOUNDATION_EXTERN void SCReportUsageEvent(NSString *eventName, NSDictionary *_Nullable eventInfo); 84 | 85 | 86 | #pragma mark - User Permissions API 87 | 88 | /*! 89 | * @typedef SCUserEmailSharingResponse 90 | * @brief A list of a user’s possible actions in response to an email sharing dialog. 91 | */ 92 | typedef NS_ENUM(NSInteger, SCUserEmailSharingResponse) 93 | { 94 | /// User hasn’t seen the dialog yet. 95 | SCUserEmailSharingResponseAbsent = 0, 96 | 97 | /// User has made a choice (allow or forbid email sharing). 98 | SCUserEmailSharingResponseMadeChoice, 99 | 100 | /// User has just closed the dialog without making a choice. 101 | SCUserEmailSharingResponseAskLater, 102 | 103 | /// The app couldn't connect to the Setapp Agent. 104 | SCUserEmailSharingResponseUndefined = NSNotFound 105 | }; 106 | 107 | /*! @brief Get the user’s last action in response to the email sharing dialog. 108 | * @return Returns the user’s last action. 109 | */ 110 | FOUNDATION_EXTERN SCUserEmailSharingResponse SCGetLastUserEmailSharingResponse(void); 111 | 112 | /*! @brief Shows the dialog that offers sharing an email address 113 | * @discussion Although you should call this method when it makes sense in the user experience flow of your app, the actual display of an email sharing dialog is governed by Setapp policy. 114 | * For example, the dialog won't show if user has already shared the email or one was recently shown and user selected "later" option. 115 | * Each time user selects "later" option, the presentation cooldown increases (up to one mounth). 116 | * @param completionHandler Completion block with the user’s response as an input param. 117 | * @returns YES if the dialog was shown. Otherwise returns NO. 118 | */ 119 | FOUNDATION_EXTERN BOOL SCAskUserToShareEmail(void (^_Nullable completionHandler)(SCUserEmailSharingResponse userResponse)); 120 | 121 | 122 | #pragma mark - Debug Logging API 123 | 124 | /*! @brief Enables debug logging of Setapp Library. 125 | * @discussion Disable debug logging in release builds. 126 | * @code 127 | * #ifdef DEBUG 128 | * SCEnableDebugLogging(YES); 129 | * #endif 130 | * @endcode 131 | * @param shouldEnable If set to YES, enables debug logging. If NO, disables it. 132 | */ 133 | FOUNDATION_EXTERN void SCEnableDebugLogging(BOOL shouldEnable); 134 | 135 | NS_ASSUME_NONNULL_END 136 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/libSetapp/libSetapp.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/src/binaries/setapp-nodejs-wrapper/libSetapp/libSetapp.a -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setapp-lib-nodejs", 3 | "version": "0.1.0", 4 | "description": "Setapp lib node.js wrapper", 5 | "private": true, 6 | "dependencies": { 7 | "bindings": "^1.2.1", 8 | "electron-rebuild": "^2.3.5", 9 | "nan": "^2.4.0" 10 | }, 11 | "scripts": { 12 | "build": "node-gyp rebuild --target=12.20.0 --arch=x64 --dist-url=https://electronjs.org/headers; ./node_modules/.bin/electron-rebuild", 13 | "rebuild": "./node_modules/.bin/electron-rebuild --arch=arm64; ./node_modules/.bin/electron-rebuild --arch=x64;" 14 | }, 15 | "gypfile": true, 16 | "devDependencies": { 17 | "node-gyp": "^3.4.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/binaries/setapp-nodejs-wrapper/setapp-lib-mapping.mm: -------------------------------------------------------------------------------- 1 | #import "Setapp.h" 2 | #import 3 | 4 | using v8::Context; 5 | using v8::Isolate; 6 | using v8::Local; 7 | using v8::Object; 8 | using v8::FunctionCallbackInfo; 9 | using v8::FunctionTemplate; 10 | using v8::Function; 11 | using v8::String; 12 | using v8::Value; 13 | using v8::NewStringType; 14 | using v8::Exception; 15 | 16 | #pragma mark - Release Notes API 17 | 18 | void _SCShowReleaseNotesWindowIfNeeded(const FunctionCallbackInfo& info) 19 | { 20 | SCShowReleaseNotesWindowIfNeeded(); 21 | } 22 | 23 | void _SCShowReleaseNotesWindow(const FunctionCallbackInfo& info) 24 | { 25 | SCShowReleaseNotesWindow(); 26 | } 27 | 28 | void _SCCanShowReleaseNotesWindow(const FunctionCallbackInfo& info) 29 | { 30 | BOOL canShow = SCCanShowReleaseNotesWindow(); 31 | info.GetReturnValue().Set(canShow ? Nan::True() : Nan::False()); 32 | } 33 | 34 | #pragma mark - Usage Events API 35 | 36 | void _SCReportUsageEvent(const FunctionCallbackInfo& info) 37 | { 38 | Isolate* isolate = info.GetIsolate(); 39 | 40 | if (info.Length() < 1) 41 | { 42 | Nan::ThrowTypeError("Wrong number of arguments. Should be at least 1 string arg."); 43 | return; 44 | } 45 | 46 | String::Utf8Value utf8Str(isolate, info[0].As()); 47 | NSString *eventName = [NSString stringWithUTF8String:*utf8Str] ?: @""; 48 | 49 | if (info.Length() > 1) 50 | { 51 | Nan::ThrowTypeError("Second argument is not supportted rignt now"); 52 | } 53 | 54 | SCReportUsageEvent(eventName, nil); 55 | } 56 | 57 | #pragma mark - User Permissions API 58 | 59 | void _SCGetLastUserEmailSharingResponse(const FunctionCallbackInfo& info) 60 | { 61 | SCUserEmailSharingResponse response = SCGetLastUserEmailSharingResponse(); 62 | info.GetReturnValue().Set(Nan::New((int32_t)response)); 63 | } 64 | 65 | void _SCAskUserToShareEmail(const FunctionCallbackInfo& info) 66 | { 67 | void (^completionHandler)(SCUserEmailSharingResponse) = nil; 68 | if (info.Length() > 0) 69 | { 70 | Nan::Callback *pCallback = new Nan::Callback(info[0].As()); 71 | completionHandler = ^(SCUserEmailSharingResponse response) { 72 | v8::Local argv[] = { Nan::New((int32_t)response) }; 73 | pCallback->Call(1, argv); 74 | delete pCallback; 75 | }; 76 | } 77 | BOOL didShowDialog = SCAskUserToShareEmail(completionHandler); 78 | info.GetReturnValue().Set(didShowDialog ? Nan::True() : Nan::False()); 79 | } 80 | 81 | #pragma mark - Debug Logging API 82 | 83 | void _SCEnableDebugLogging(const FunctionCallbackInfo& info) 84 | { 85 | if (info.Length() < 1) 86 | { 87 | Nan::ThrowTypeError("Wrong number of arguments. Should be 1."); 88 | return; 89 | } 90 | 91 | if (!info[0]->IsBoolean()) 92 | { 93 | Nan::ThrowTypeError("Wrong argument type. Should be Boolean."); 94 | return; 95 | } 96 | 97 | BOOL shouldEnable = info[0].As()->Value() ? YES : NO; 98 | SCEnableDebugLogging(shouldEnable); 99 | } 100 | 101 | /**********************************************************/ 102 | void Init(Local exports) 103 | { 104 | Isolate* isolate = exports->GetIsolate(); 105 | Local context = isolate->GetCurrentContext(); 106 | 107 | // Release Notes API 108 | exports->Set(context, 109 | String::NewFromUtf8(isolate, "SCShowReleaseNotesWindowIfNeeded", NewStringType::kNormal).ToLocalChecked(), 110 | FunctionTemplate::New(isolate, _SCShowReleaseNotesWindowIfNeeded)->GetFunction(context).ToLocalChecked()); 111 | exports->Set(context, 112 | String::NewFromUtf8(isolate, "SCShowReleaseNotesWindow", NewStringType::kNormal).ToLocalChecked(), 113 | FunctionTemplate::New(isolate, _SCShowReleaseNotesWindow)->GetFunction(context).ToLocalChecked()); 114 | exports->Set(context, 115 | String::NewFromUtf8(isolate, "SCCanShowReleaseNotesWindow", NewStringType::kNormal).ToLocalChecked(), 116 | FunctionTemplate::New(isolate, _SCCanShowReleaseNotesWindow)->GetFunction(context).ToLocalChecked()); 117 | 118 | // Usage Events API 119 | exports->Set(context, 120 | String::NewFromUtf8(isolate, "SCReportUsageEvent", NewStringType::kNormal).ToLocalChecked(), 121 | FunctionTemplate::New(isolate, _SCReportUsageEvent)->GetFunction(context).ToLocalChecked()); 122 | // User Permissions API 123 | exports->Set(context, 124 | String::NewFromUtf8(isolate, "SCGetLastUserEmailSharingResponse", NewStringType::kNormal).ToLocalChecked(), 125 | FunctionTemplate::New(isolate, _SCGetLastUserEmailSharingResponse)->GetFunction(context).ToLocalChecked()); 126 | exports->Set(context, 127 | String::NewFromUtf8(isolate, "SCAskUserToShareEmail", NewStringType::kNormal).ToLocalChecked(), 128 | FunctionTemplate::New(isolate, _SCAskUserToShareEmail)->GetFunction(context).ToLocalChecked()); 129 | 130 | // Debug Logging API 131 | exports->Set(context, 132 | String::NewFromUtf8(isolate, "SCEnableDebugLogging", NewStringType::kNormal).ToLocalChecked(), 133 | FunctionTemplate::New(isolate, _SCEnableDebugLogging)->GetFunction(context).ToLocalChecked()); 134 | } 135 | 136 | NODE_MODULE(setapp, Init) 137 | -------------------------------------------------------------------------------- /src/build/build-setapp-legacy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env; 3 | echo "Building Setapp wrapper!"; 4 | npm install --unsafe-perm; 5 | npm install --unsafe-perm --prefix ./setapp-nodejs-wrapper; 6 | npm run build --unsafe-perm --prefix ./setapp-nodejs-wrapper; 7 | npm run rebuild --unsafe-perm --prefix ./setapp-nodejs-wrapper; 8 | echo "Finished building Setapp wrapper!"; 9 | source .env; 10 | export IS_SETAPP=true; -------------------------------------------------------------------------------- /src/build/build-setapp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env; 3 | export IS_SETAPP=true; 4 | 5 | npm install --unsafe-perm; 6 | 7 | # build local setapp wrapper 8 | echo "Building Setapp wrapper!"; 9 | npm install --unsafe-perm --prefix ./binaries/setapp-nodejs-wrapper; 10 | npm run rebuild --unsafe-perm --prefix ./binaries/setapp-nodejs-wrapper; 11 | # copy binaries out, create setapp fat 12 | lipo ./binaries/setapp-nodejs-wrapper/bin/darwin-arm64-85/setapp-nodejs-wrapper.node ./binaries/setapp-nodejs-wrapper/bin/darwin-x64-85/setapp-nodejs-wrapper.node -create -output ./binaries/setapp-nodejs-wrapper.node; 13 | lipo -i ./binaries/setapp-nodejs-wrapper.node; 14 | echo "Finished building Setapp wrapper!"; 15 | 16 | # arm64 steps (apple silicon) 17 | echo "Rebuilding all native modules in project for ARM64..."; 18 | npm run rebuild-arm64; 19 | 20 | # x64 steps (intel mac) 21 | echo "Rebuilding all native modules in project for x64..."; 22 | npm run rebuild-x64; 23 | 24 | # convert x64/arm64 binaries into glued fats 25 | echo "Lipo native modules to Universal type..."; 26 | # node-mac-permissions 27 | lipo ./node_modules/node-mac-permissions/bin/darwin-arm64-85/node-mac-permissions.node ./node_modules/node-mac-permissions/bin/darwin-x64-85/node-mac-permissions.node -create -output ./node_modules/node-mac-permissions/build/Release/permissions.node; 28 | lipo -i ./node_modules/node-mac-permissions/build/Release/permissions.node; 29 | # sqlite3 30 | lipo ./node_modules/sqlite3/bin/darwin-arm64-85/sqlite3.node ./node_modules/sqlite3/bin/darwin-x64-85/sqlite3.node -create -output ./node_modules/sqlite3/build/Release/node_sqlite3.node; 31 | lipo -i ./node_modules/sqlite3/build/Release/node_sqlite3.node; 32 | echo "Done lipo-ing native modules to Universal type!"; 33 | 34 | # build steps 35 | echo "Building ARM64..."; 36 | npm run build-setapp-arm64; 37 | echo "Done building ARM64!"; 38 | 39 | echo "Building x64..."; 40 | npm run build-setapp-x64; 41 | echo "Done building x64!"; 42 | 43 | # universal steps (intel/arm64 mac) 44 | echo "Building universal app..."; 45 | npm run build-universal; 46 | echo "Done building universal app!" -------------------------------------------------------------------------------- /src/build/build-universal.js: -------------------------------------------------------------------------------- 1 | const { 2 | makeUniversalApp 3 | } = require('./universal'); 4 | const path = require("path"); 5 | 6 | async function main() { 7 | // https://github.com/deepak1556/universal 8 | // https://fossies.org/linux/vscode/build/darwin/create-universal-app.ts 9 | await makeUniversalApp({ 10 | filesToSkip: [ 11 | 'product.json', 12 | 'Credits.rtf', 13 | 'CodeResources', 14 | 'fsevents.node', 15 | 'Info.plist', 16 | '.npmrc' 17 | ], 18 | x64AppPath: path.join(__dirname, '../dist', 'mac', 'Ohtipi.app'), 19 | arm64AppPath: path.join(__dirname, '../dist', 'mac-arm64', 'Ohtipi.app'), 20 | x64AsarPath: path.join(__dirname, '../dist', 'mac', 'Ohtipi.app', 'Contents', 'Resources', 'app.asar'), 21 | arm64AsarPath: path.join(__dirname, '../dist', 'mac-arm64', 'Ohtipi.app', 'Contents', 'Resources', 'app.asar'), 22 | outAppPath: path.join(__dirname, '../dist/mac-universal/Ohtipi.app'), 23 | force: true 24 | }); 25 | } 26 | 27 | main() -------------------------------------------------------------------------------- /src/build/build-universal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env; 3 | export IS_SETAPP=false; 4 | 5 | npm install --unsafe-perm; 6 | 7 | # arm64 steps (apple silicon) 8 | echo "Rebuilding all native modules in project for ARM64..."; 9 | npm run rebuild-arm64; 10 | 11 | # x64 steps (intel mac) 12 | echo "Rebuilding all native modules in project for x64..."; 13 | npm run rebuild-x64; 14 | 15 | # convert x64/arm64 binaries into glued fats 16 | echo "Lipo native modules to Universal type..."; 17 | # node-mac-permissions 18 | lipo ./node_modules/node-mac-permissions/bin/darwin-arm64-85/node-mac-permissions.node ./node_modules/node-mac-permissions/bin/darwin-x64-85/node-mac-permissions.node -create -output ./node_modules/node-mac-permissions/build/Release/permissions.node; 19 | lipo -i ./node_modules/node-mac-permissions/build/Release/permissions.node; 20 | # sqlite3 21 | lipo ./node_modules/sqlite3/bin/darwin-arm64-85/sqlite3.node ./node_modules/sqlite3/bin/darwin-x64-85/sqlite3.node -create -output ./node_modules/sqlite3/build/Release/node_sqlite3.node; 22 | lipo -i ./node_modules/sqlite3/build/Release/node_sqlite3.node; 23 | echo "Done lipo-ing native modules to Universal type!"; 24 | 25 | # build steps 26 | echo "Building ARM64..."; 27 | npm run build-setapp-arm64; 28 | echo "Done building ARM64!"; 29 | 30 | echo "Building x64..."; 31 | npm run build-setapp-x64; 32 | echo "Done building x64!"; 33 | 34 | # universal steps (intel/arm64 mac) 35 | echo "Building universal app..."; 36 | npm run build-universal; 37 | 38 | echo "Codesigning universal app..."; 39 | # you may need to change the cert profile identifier signature 40 | # note: must use hardened runtime option 41 | codesign -fv --deep -s CBA966CA6CC43CD196A9372C0AE024B0D839A4E5 ./dist/mac-universal/Ohtipi.app --options runtime --entitlements ./build/entitlements.mac.plist; 42 | 43 | echo "Notarizing universal app..."; 44 | npm run setapp-notarize; 45 | 46 | echo "Universal app is ready for distribution!"; -------------------------------------------------------------------------------- /src/build/electron.arm64.config.js: -------------------------------------------------------------------------------- 1 | const config = require("../config.js"); 2 | 3 | module.exports = { 4 | copyright: "© 2022, Yac Inc.", 5 | appId: config.build.setApp ? "com.ohtipi.app-setapp" : "com.ohtipi.app", 6 | icon: "./assets/dock/dock-icon.icns", 7 | files: [ 8 | "./**/*", 9 | "./**/**/*", 10 | "node_modules/**/*", 11 | "package.json", 12 | // include extra native modules if setapp 13 | config.build.setApp || config.build.universal ? "binaries/*.node" : undefined, 14 | ], 15 | npmRebuild: false, 16 | forceCodeSigning: true, 17 | afterSign: "build/notarize.js", 18 | mac: { 19 | hardenedRuntime: true, 20 | gatekeeperAssess: false, 21 | entitlements: "build/entitlements.mac.plist", 22 | entitlementsInherit: "build/entitlements.mac.plist", 23 | type: "distribution", 24 | darkModeSupport: true, 25 | artifactName: "${productName}-${arch}-${version}.${ext}", 26 | target: [{ 27 | target: "zip", 28 | arch: ["arm64"] 29 | }], 30 | // don't publish if setapp 31 | publish: !config.build.setApp && !config.build.universal ? { 32 | provider: "s3", 33 | bucket: "ohtipi-release", 34 | region: "us-east-1" 35 | } : undefined 36 | }, 37 | } -------------------------------------------------------------------------------- /src/build/electron.config.js: -------------------------------------------------------------------------------- 1 | const config = require("../config.js"); 2 | 3 | module.exports = { 4 | copyright: "© 2022, Yac Inc.", 5 | appId: config.build.setApp ? "com.ohtipi.app-setapp" : "com.ohtipi.app", 6 | icon: "./assets/dock/dock-icon.icns", 7 | files: [ 8 | "./**/*", 9 | "./**/**/*", 10 | "node_modules/**/*", 11 | "package.json" 12 | ], 13 | mac: { 14 | hardenedRuntime: true, 15 | gatekeeperAssess: false, 16 | entitlements: "build/entitlements.mac.plist", 17 | entitlementsInherit: "build/entitlements.mac.plist", 18 | type: "distribution", 19 | darkModeSupport: true, 20 | artifactName: "${productName}-${arch}-${version}.${ext}", 21 | target: [ 22 | { 23 | target: "default", 24 | arch: ["x64", "arm64"] 25 | } 26 | ], 27 | publish: { 28 | provider: "s3", 29 | bucket: "ohtipi-release", 30 | region: "us-east-1" 31 | } 32 | }, 33 | dmg: { 34 | sign: false 35 | }, 36 | afterSign: "build/notarize.js" 37 | } -------------------------------------------------------------------------------- /src/build/electron.x64.config.js: -------------------------------------------------------------------------------- 1 | const config = require("../config.js"); 2 | 3 | module.exports = { 4 | copyright: "© 2022, Yac Inc.", 5 | appId: config.build.setApp ? "com.ohtipi.app-setapp" : "com.ohtipi.app", 6 | icon: "./assets/dock/dock-icon.icns", 7 | files: [ 8 | "./**/*", 9 | "./**/**/*", 10 | "node_modules/**/*", 11 | "package.json", 12 | // include extra native modules if setapp 13 | config.build.setApp || config.build.universal ? "binaries/*.node" : undefined, 14 | ], 15 | npmRebuild: false, 16 | forceCodeSigning: true, 17 | afterSign: "build/notarize.js", 18 | mac: { 19 | hardenedRuntime: true, 20 | gatekeeperAssess: false, 21 | entitlements: "build/entitlements.mac.plist", 22 | entitlementsInherit: "build/entitlements.mac.plist", 23 | type: "distribution", 24 | darkModeSupport: true, 25 | artifactName: "${productName}-${arch}-${version}.${ext}", 26 | target: [{ 27 | target: "zip", 28 | arch: ["x64"] 29 | }], 30 | // don't publish if setapp 31 | publish: !config.build.setApp && !config.build.universal ? { 32 | provider: "s3", 33 | bucket: "ohtipi-release", 34 | region: "us-east-1" 35 | } : undefined 36 | }, 37 | } -------------------------------------------------------------------------------- /src/build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.disable-library-validation 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.cs.allow-unsigned-executable-memory 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | com.apple.security.cs.allow-dyld-environment-variables 14 | 15 | com.apple.security.cs.allow-jit 16 | 17 | com.apple.security.automation.apple-events 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/build/notarize.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const config = require("../config.js"); 3 | 4 | const { 5 | notarize 6 | } = require('electron-notarize'); 7 | 8 | exports.default = async function notarizing(context) { 9 | const { 10 | electronPlatformName, 11 | appOutDir 12 | } = context; 13 | if (electronPlatformName !== 'darwin') { 14 | return; 15 | } 16 | 17 | const appName = context.packager.appInfo.productFilename; 18 | 19 | return await notarize({ 20 | appBundleId: config.build.setApp ? 'com.ohtipi.app-setapp' : 'com.ohtipi.app', 21 | appPath: `${appOutDir}/${appName}.app`, 22 | appleId: process.env.APPLEID, 23 | appleIdPassword: process.env.APPLEIDPASS, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/build/setapp-notarize.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | require('dotenv').config(); 3 | 4 | const { 5 | notarize 6 | } = require('electron-notarize'); 7 | 8 | async function main() { 9 | await notarize({ 10 | appBundleId: 'com.ohtipi.app-setapp', 11 | appPath: path.join(__dirname, '../dist/mac-universal/Ohtipi.app'), 12 | appleId: process.env.APPLEID, 13 | appleIdPassword: process.env.APPLEIDPASS, 14 | }); 15 | console.log("Finished notarizing universal Setapp bundle!"); 16 | } 17 | 18 | 19 | main() -------------------------------------------------------------------------------- /src/build/universal/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "parser": "typescript" 7 | } -------------------------------------------------------------------------------- /src/build/universal/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | "@continuous-auth/semantic-release-npm", 6 | "@semantic-release/github" 7 | ] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/build/universal/README.md: -------------------------------------------------------------------------------- 1 | # @electron/universal 2 | 3 | > Create universal macOS Electron applications 4 | 5 | [![CircleCI](https://circleci.com/gh/electron/universal/tree/master.svg?style=svg)](https://circleci.com/gh/electron/universal) 6 | 7 | 8 | ## Usage 9 | 10 | ```typescript 11 | import { makeUniversalApp } from '@electron/universal'; 12 | 13 | await makeUniversalApp({ 14 | x64AppPath: 'path/to/App_x64.app', 15 | arm64AppPath: 'path/to/App_arm64.app', 16 | outAppPath: 'path/to/App_universal.app', 17 | }); 18 | ``` 19 | 20 | ## FAQ 21 | 22 | #### The app is twice as big now, why? 23 | 24 | Well, a Universal app isn't anything magical. It is literally the x64 app and 25 | the arm64 app glued together into a single application. It's twice as big 26 | because it contains two apps in one. 27 | 28 | #### What about native modules? 29 | 30 | The way `@electron/universal` works today means you don't need to worry about 31 | things like building universal versions of your native modules. As long as 32 | your x64 and arm64 apps work in isolation the Universal app will work as well. 33 | 34 | #### How do I build my app for Apple silicon in the first place? 35 | 36 | Check out the [Electron Apple silicon blog post](https://www.electronjs.org/blog/apple-silicon) 37 | -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/asar-utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum AsarMode { 2 | NO_ASAR = 0, 3 | HAS_ASAR = 1, 4 | } 5 | export declare const detectAsarMode: ( 6 | appPath: string, 7 | asarPath?: string | undefined, 8 | ) => Promise; 9 | -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/asar-utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.detectAsarMode = exports.AsarMode = void 0; 4 | const fs = require("fs-extra"); 5 | const path = require("path"); 6 | const debug_1 = require("./debug"); 7 | var AsarMode; 8 | (function (AsarMode) { 9 | AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR"; 10 | AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR"; 11 | })(AsarMode = exports.AsarMode || (exports.AsarMode = {})); 12 | const detectAsarMode = async (appPath, asarPath) => { 13 | debug_1.d('checking asar mode of', appPath); 14 | const result = asarPath !== null && asarPath !== void 0 ? asarPath : path.resolve(appPath, 'Contents', 'Resources', 'app.asar'); 15 | if (!(await fs.pathExists(result))) { 16 | debug_1.d('determined no asar'); 17 | return AsarMode.NO_ASAR; 18 | } 19 | debug_1.d('determined has asar'); 20 | return AsarMode.HAS_ASAR; 21 | }; 22 | exports.detectAsarMode = detectAsarMode; 23 | //# sourceMappingURL=asar-utils.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/asar-utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"asar-utils.js","sourceRoot":"","sources":["../../src/asar-utils.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAC/B,6BAA6B;AAC7B,mCAA4B;AAE5B,IAAY,QAGX;AAHD,WAAY,QAAQ;IAClB,6CAAO,CAAA;IACP,+CAAQ,CAAA;AACV,CAAC,EAHW,QAAQ,GAAR,gBAAQ,KAAR,gBAAQ,QAGnB;AAEM,MAAM,cAAc,GAAG,KAAK,EAAE,OAAe,EAAE,QAAiB,EAAE,EAAE;IACzE,SAAC,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAEtF,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE;QAClC,SAAC,CAAC,oBAAoB,CAAC,CAAC;QACxB,OAAO,QAAQ,CAAC,OAAO,CAAC;KACzB;IAED,SAAC,CAAC,qBAAqB,CAAC,CAAC;IACzB,OAAO,QAAQ,CAAC,QAAQ,CAAC;AAC3B,CAAC,CAAC;AAXW,QAAA,cAAc,kBAWzB"} -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/debug.d.ts: -------------------------------------------------------------------------------- 1 | import * as debug from 'debug'; 2 | export declare const d: debug.Debugger; 3 | -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/debug.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.d = void 0; 4 | const debug = require("debug"); 5 | exports.d = debug('electron-universal'); 6 | //# sourceMappingURL=debug.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/debug.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAElB,QAAA,CAAC,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC"} -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/file-utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum AppFileType { 2 | MACHO = 0, 3 | PLAIN = 1, 4 | SNAPSHOT = 2, 5 | APP_CODE = 3, 6 | } 7 | export declare type AppFile = { 8 | relativePath: string; 9 | type: AppFileType; 10 | }; 11 | /** 12 | * 13 | * @param appPath Path to the application 14 | */ 15 | export declare const getAllAppFiles: ( 16 | appPath: string, 17 | filesToSkip?: string[] | undefined, 18 | ) => Promise; 19 | -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/file-utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getAllAppFiles = exports.AppFileType = void 0; 4 | const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); 5 | const fs = require("fs-extra"); 6 | const path = require("path"); 7 | const MACHO_PREFIX = 'Mach-O '; 8 | var AppFileType; 9 | (function (AppFileType) { 10 | AppFileType[AppFileType["MACHO"] = 0] = "MACHO"; 11 | AppFileType[AppFileType["PLAIN"] = 1] = "PLAIN"; 12 | AppFileType[AppFileType["SNAPSHOT"] = 2] = "SNAPSHOT"; 13 | AppFileType[AppFileType["APP_CODE"] = 3] = "APP_CODE"; 14 | })(AppFileType = exports.AppFileType || (exports.AppFileType = {})); 15 | /** 16 | * 17 | * @param appPath Path to the application 18 | */ 19 | const getAllAppFiles = async (appPath, filesToSkip) => { 20 | const files = []; 21 | const visited = new Set(); 22 | const traverse = async (p) => { 23 | p = await fs.realpath(p); 24 | if (filesToSkip === null || filesToSkip === void 0 ? void 0 : filesToSkip.find((e) => p.includes(e))) 25 | return; 26 | if (visited.has(p)) 27 | return; 28 | visited.add(p); 29 | const info = await fs.stat(p); 30 | if (info.isSymbolicLink()) 31 | return; 32 | if (info.isFile()) { 33 | let fileType = AppFileType.PLAIN; 34 | if (!p.endsWith('.wasm')) { 35 | const fileOutput = await cross_spawn_promise_1.spawn('file', ['--brief', '--no-pad', p]); 36 | if (p.endsWith('.asar')) { 37 | fileType = AppFileType.APP_CODE; 38 | } 39 | else if (fileOutput.startsWith(MACHO_PREFIX)) { 40 | fileType = AppFileType.MACHO; 41 | } 42 | else if (p.endsWith('.bin')) { 43 | fileType = AppFileType.SNAPSHOT; 44 | } 45 | } 46 | files.push({ 47 | relativePath: path.relative(appPath, p), 48 | type: fileType, 49 | }); 50 | } 51 | if (info.isDirectory()) { 52 | for (const child of await fs.readdir(p)) { 53 | await traverse(path.resolve(p, child)); 54 | } 55 | } 56 | }; 57 | await traverse(appPath); 58 | return files; 59 | }; 60 | exports.getAllAppFiles = getAllAppFiles; 61 | //# sourceMappingURL=file-utils.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/file-utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file-utils.ts"],"names":[],"mappings":";;;AAAA,qEAAoD;AACpD,+BAA+B;AAC/B,6BAA6B;AAE7B,MAAM,YAAY,GAAG,SAAS,CAAC;AAE/B,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,+CAAK,CAAA;IACL,+CAAK,CAAA;IACL,qDAAQ,CAAA;IACR,qDAAQ,CAAA;AACV,CAAC,EALW,WAAW,GAAX,mBAAW,KAAX,mBAAW,QAKtB;AAOD;;;GAGG;AACI,MAAM,cAAc,GAAG,KAAK,EACjC,OAAe,EACf,WAA2B,EACP,EAAE;IACtB,MAAM,KAAK,GAAc,EAAE,CAAC;IAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAS,EAAE,EAAE;QACnC,CAAC,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAG,OAAO;QACpD,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO;QAC3B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAEf,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,cAAc,EAAE;YAAE,OAAO;QAClC,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE;YACjB,IAAI,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC;YAEjC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;gBACxB,MAAM,UAAU,GAAG,MAAM,2BAAK,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;gBACnE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;oBACvB,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;iBACjC;qBAAM,IAAI,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;oBAC9C,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC;iBAC9B;qBAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;oBAC7B,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;iBACjC;aACF;YAED,KAAK,CAAC,IAAI,CAAC;gBACT,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;gBACvC,IAAI,EAAE,QAAQ;aACf,CAAC,CAAC;SACJ;QAED,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;YACtB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;gBACvC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;aACxC;SACF;IACH,CAAC,CAAC;IACF,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAExB,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AA5CW,QAAA,cAAc,kBA4CzB"} -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/index.d.ts: -------------------------------------------------------------------------------- 1 | declare type MakeUniversalOpts = { 2 | /** 3 | * Absolute file system path to the x64 version of your application. E.g. /Foo/bar/MyApp_x64.app 4 | */ 5 | x64AppPath: string; 6 | /** 7 | * Absolute file system path to the arm64 version of your application. E.g. /Foo/bar/MyApp_arm64.app 8 | */ 9 | arm64AppPath: string; 10 | x64AsarPath?: string; 11 | arm64AsarPath?: string; 12 | filesToSkip?: Array; 13 | /** 14 | * Absolute file system path you want the universal app to be written to. E.g. /Foo/var/MyApp_universal.app 15 | * 16 | * If this file exists it will be overwritten ONLY if "force" is set to true 17 | */ 18 | outAppPath: string; 19 | /** 20 | * Forcefully overwrite any existing files that are in the way of generating the universal application 21 | */ 22 | force: boolean; 23 | }; 24 | export declare const makeUniversalApp: (opts: MakeUniversalOpts) => Promise; 25 | export {}; 26 | -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.makeUniversalApp = void 0; 4 | const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); 5 | const asar = require("asar"); 6 | const fs = require("fs-extra"); 7 | const os = require("os"); 8 | const path = require("path"); 9 | const dircompare = require("dir-compare"); 10 | const file_utils_1 = require("./file-utils"); 11 | const asar_utils_1 = require("./asar-utils"); 12 | const sha_1 = require("./sha"); 13 | const debug_1 = require("./debug"); 14 | const dupedFiles = (files) => files.filter((f) => f.type !== file_utils_1.AppFileType.SNAPSHOT && f.type !== file_utils_1.AppFileType.APP_CODE); 15 | const makeUniversalApp = async (opts) => { 16 | var _a, _b; 17 | debug_1.d('making a universal app with options', opts); 18 | if (process.platform !== 'darwin') 19 | throw new Error('@electron/universal is only supported on darwin platforms'); 20 | if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath)) 21 | throw new Error('Expected opts.x64AppPath to be an absolute path but it was not'); 22 | if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath)) 23 | throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not'); 24 | if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath)) 25 | throw new Error('Expected opts.outAppPath to be an absolute path but it was not'); 26 | if (await fs.pathExists(opts.outAppPath)) { 27 | debug_1.d('output path exists already'); 28 | if (!opts.force) { 29 | throw new Error(`The out path "${opts.outAppPath}" already exists and force is not set to true`); 30 | } 31 | else { 32 | debug_1.d('overwriting existing application because force == true'); 33 | await fs.remove(opts.outAppPath); 34 | } 35 | } 36 | const x64AsarMode = await asar_utils_1.detectAsarMode(opts.x64AppPath, opts.x64AsarPath); 37 | const arm64AsarMode = await asar_utils_1.detectAsarMode(opts.arm64AppPath, opts.arm64AsarPath); 38 | debug_1.d('detected x64AsarMode =', x64AsarMode); 39 | debug_1.d('detected arm64AsarMode =', arm64AsarMode); 40 | if (x64AsarMode !== arm64AsarMode) 41 | throw new Error('Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)'); 42 | const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-')); 43 | debug_1.d('building universal app in', tmpDir); 44 | try { 45 | debug_1.d('copying x64 app as starter template'); 46 | const tmpApp = path.resolve(tmpDir, 'Tmp.app'); 47 | await cross_spawn_promise_1.spawn('cp', ['-R', opts.x64AppPath, tmpApp]); 48 | const uniqueToX64 = []; 49 | const uniqueToArm64 = []; 50 | const x64Files = await file_utils_1.getAllAppFiles(await fs.realpath(tmpApp), opts.filesToSkip); 51 | const arm64Files = await file_utils_1.getAllAppFiles(await fs.realpath(opts.arm64AppPath), opts.filesToSkip); 52 | for (const file of dupedFiles(x64Files)) { 53 | if (!arm64Files.some((f) => f.relativePath === file.relativePath)) 54 | uniqueToX64.push(file.relativePath); 55 | } 56 | for (const file of dupedFiles(arm64Files)) { 57 | if (!x64Files.some((f) => f.relativePath === file.relativePath)) 58 | uniqueToArm64.push(file.relativePath); 59 | } 60 | if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) { 61 | debug_1.d('some files were not in both builds, aborting'); 62 | console.error({ 63 | uniqueToX64, 64 | uniqueToArm64, 65 | }); 66 | throw new Error('While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds'); 67 | } 68 | for (const file of x64Files.filter((f) => f.type === file_utils_1.AppFileType.PLAIN)) { 69 | const x64Sha = await sha_1.sha(path.resolve(opts.x64AppPath, file.relativePath)); 70 | const arm64Sha = await sha_1.sha(path.resolve(opts.arm64AppPath, file.relativePath)); 71 | if (x64Sha !== arm64Sha) { 72 | debug_1.d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`); 73 | throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`); 74 | } 75 | } 76 | for (const machOFile of x64Files.filter((f) => f.type === file_utils_1.AppFileType.MACHO)) { 77 | const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)); 78 | const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath)); 79 | debug_1.d('joining two MachO files with lipo', { 80 | first, 81 | second, 82 | }); 83 | await cross_spawn_promise_1.spawn('lipo', [ 84 | first, 85 | second, 86 | '-create', 87 | '-output', 88 | await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)), 89 | ]); 90 | } 91 | /** 92 | * If we don't have an ASAR we need to check if the two "app" folders are identical, if 93 | * they are then we can just leave one there and call it a day. If the app folders for x64 94 | * and arm64 are different though we need to rename each folder and create a new fake "app" 95 | * entrypoint to dynamically load the correct app folder 96 | */ 97 | if (x64AsarMode === asar_utils_1.AsarMode.NO_ASAR) { 98 | debug_1.d('checking if the x64 and arm64 app folders are identical'); 99 | const comparison = await dircompare.compare(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), { compareSize: true, compareContent: true }); 100 | if (!comparison.same) { 101 | debug_1.d('x64 and arm64 app folders are different, creating dynamic entry ASAR'); 102 | await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64')); 103 | await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64')); 104 | const entryAsar = path.resolve(tmpDir, 'entry-asar'); 105 | await fs.mkdir(entryAsar); 106 | await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), path.resolve(entryAsar, 'index.js')); 107 | let pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json')); 108 | pj.main = 'index.js'; 109 | await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); 110 | await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar')); 111 | } 112 | else { 113 | debug_1.d('x64 and arm64 app folders are the same'); 114 | } 115 | } 116 | /** 117 | * If we have an ASAR we just need to check if the two "app.asar" files have the same hash, 118 | * if they are, same as above, we can leave one there and call it a day. If they're different 119 | * we have to make a dynamic entrypoint. There is an assumption made here that every file in 120 | * app.asar.unpacked is a native node module. This assumption _may_ not be true so we should 121 | * look at codifying that assumption as actual logic. 122 | */ 123 | // FIXME: Codify the assumption that app.asar.unpacked only contains native modules 124 | if (x64AsarMode === asar_utils_1.AsarMode.HAS_ASAR) { 125 | debug_1.d('checking if the x64 and arm64 asars are identical'); 126 | const x64AsarSha = await sha_1.sha((_a = opts.x64AsarPath) !== null && _a !== void 0 ? _a : path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar')); 127 | const arm64AsarSha = await sha_1.sha((_b = opts.arm64AsarPath) !== null && _b !== void 0 ? _b : path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar')); 128 | if (x64AsarSha !== arm64AsarSha) { 129 | debug_1.d('x64 and arm64 asars are different'); 130 | /*await fs.move( 131 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 132 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'), 133 | ); 134 | const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked'); 135 | if (await fs.pathExists(x64Unpacked)) { 136 | await fs.move( 137 | x64Unpacked, 138 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'), 139 | ); 140 | } 141 | 142 | await fs.copy( 143 | path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), 144 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar'), 145 | ); 146 | const arm64Unpacked = path.resolve( 147 | opts.arm64AppPath, 148 | 'Contents', 149 | 'Resources', 150 | 'app.asar.unpacked', 151 | ); 152 | if (await fs.pathExists(arm64Unpacked)) { 153 | await fs.copy( 154 | arm64Unpacked, 155 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'), 156 | ); 157 | } 158 | 159 | const entryAsar = path.resolve(tmpDir, 'entry-asar'); 160 | await fs.mkdir(entryAsar); 161 | await fs.copy( 162 | path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), 163 | path.resolve(entryAsar, 'index.js'), 164 | ); 165 | let pj = JSON.parse( 166 | ( 167 | await asar.extractFile( 168 | path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 169 | 'package.json', 170 | ) 171 | ).toString('utf8'), 172 | ); 173 | pj.main = 'index.js'; 174 | await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); 175 | await asar.createPackage( 176 | entryAsar, 177 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 178 | );*/ 179 | } 180 | else { 181 | debug_1.d('x64 and arm64 asars are the same'); 182 | } 183 | } 184 | for (const snapshotsFile of arm64Files.filter((f) => f.type === file_utils_1.AppFileType.SNAPSHOT)) { 185 | debug_1.d('copying snapshot file', snapshotsFile.relativePath, 'to target application'); 186 | await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath)); 187 | } 188 | debug_1.d('moving final universal app to target destination'); 189 | await fs.mkdirp(path.dirname(opts.outAppPath)); 190 | await cross_spawn_promise_1.spawn('mv', [tmpApp, opts.outAppPath]); 191 | } 192 | catch (err) { 193 | throw err; 194 | } 195 | finally { 196 | await fs.remove(tmpDir); 197 | } 198 | }; 199 | exports.makeUniversalApp = makeUniversalApp; 200 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/sha.d.ts: -------------------------------------------------------------------------------- 1 | export declare const sha: (filePath: string) => Promise; 2 | -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/sha.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.sha = void 0; 4 | const fs = require("fs-extra"); 5 | const crypto = require("crypto"); 6 | const debug_1 = require("./debug"); 7 | const sha = async (filePath) => { 8 | debug_1.d('hashing', filePath); 9 | const hash = crypto.createHash('sha256'); 10 | hash.setEncoding('hex'); 11 | const fileStream = fs.createReadStream(filePath); 12 | fileStream.pipe(hash); 13 | await new Promise((resolve, reject) => { 14 | fileStream.on('end', () => resolve()); 15 | fileStream.on('error', (err) => reject(err)); 16 | }); 17 | return hash.read(); 18 | }; 19 | exports.sha = sha; 20 | //# sourceMappingURL=sha.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/cjs/sha.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sha.js","sourceRoot":"","sources":["../../src/sha.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAC/B,iCAAiC;AACjC,mCAA4B;AAErB,MAAM,GAAG,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5C,SAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACjD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACtC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC;AAXW,QAAA,GAAG,OAWd"} -------------------------------------------------------------------------------- /src/build/universal/dist/esm/asar-utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum AsarMode { 2 | NO_ASAR = 0, 3 | HAS_ASAR = 1, 4 | } 5 | export declare const detectAsarMode: ( 6 | appPath: string, 7 | asarPath?: string | undefined, 8 | ) => Promise; 9 | -------------------------------------------------------------------------------- /src/build/universal/dist/esm/asar-utils.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as path from 'path'; 3 | import { d } from './debug'; 4 | export var AsarMode; 5 | (function (AsarMode) { 6 | AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR"; 7 | AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR"; 8 | })(AsarMode || (AsarMode = {})); 9 | export const detectAsarMode = async (appPath, asarPath) => { 10 | d('checking asar mode of', appPath); 11 | const result = asarPath !== null && asarPath !== void 0 ? asarPath : path.resolve(appPath, 'Contents', 'Resources', 'app.asar'); 12 | if (!(await fs.pathExists(result))) { 13 | d('determined no asar'); 14 | return AsarMode.NO_ASAR; 15 | } 16 | d('determined has asar'); 17 | return AsarMode.HAS_ASAR; 18 | }; 19 | //# sourceMappingURL=asar-utils.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/esm/asar-utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"asar-utils.js","sourceRoot":"","sources":["../../src/asar-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,SAAS,CAAC;AAE5B,MAAM,CAAN,IAAY,QAGX;AAHD,WAAY,QAAQ;IAClB,6CAAO,CAAA;IACP,+CAAQ,CAAA;AACV,CAAC,EAHW,QAAQ,KAAR,QAAQ,QAGnB;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,OAAe,EAAE,QAAiB,EAAE,EAAE;IACzE,CAAC,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAEtF,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE;QAClC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACxB,OAAO,QAAQ,CAAC,OAAO,CAAC;KACzB;IAED,CAAC,CAAC,qBAAqB,CAAC,CAAC;IACzB,OAAO,QAAQ,CAAC,QAAQ,CAAC;AAC3B,CAAC,CAAC"} -------------------------------------------------------------------------------- /src/build/universal/dist/esm/debug.d.ts: -------------------------------------------------------------------------------- 1 | import * as debug from 'debug'; 2 | export declare const d: debug.Debugger; 3 | -------------------------------------------------------------------------------- /src/build/universal/dist/esm/debug.js: -------------------------------------------------------------------------------- 1 | import * as debug from 'debug'; 2 | export const d = debug('electron-universal'); 3 | //# sourceMappingURL=debug.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/esm/debug.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC"} -------------------------------------------------------------------------------- /src/build/universal/dist/esm/file-utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum AppFileType { 2 | MACHO = 0, 3 | PLAIN = 1, 4 | SNAPSHOT = 2, 5 | APP_CODE = 3, 6 | } 7 | export declare type AppFile = { 8 | relativePath: string; 9 | type: AppFileType; 10 | }; 11 | /** 12 | * 13 | * @param appPath Path to the application 14 | */ 15 | export declare const getAllAppFiles: ( 16 | appPath: string, 17 | filesToSkip?: string[] | undefined, 18 | ) => Promise; 19 | -------------------------------------------------------------------------------- /src/build/universal/dist/esm/file-utils.js: -------------------------------------------------------------------------------- 1 | import { spawn } from '@malept/cross-spawn-promise'; 2 | import * as fs from 'fs-extra'; 3 | import * as path from 'path'; 4 | const MACHO_PREFIX = 'Mach-O '; 5 | export var AppFileType; 6 | (function (AppFileType) { 7 | AppFileType[AppFileType["MACHO"] = 0] = "MACHO"; 8 | AppFileType[AppFileType["PLAIN"] = 1] = "PLAIN"; 9 | AppFileType[AppFileType["SNAPSHOT"] = 2] = "SNAPSHOT"; 10 | AppFileType[AppFileType["APP_CODE"] = 3] = "APP_CODE"; 11 | })(AppFileType || (AppFileType = {})); 12 | /** 13 | * 14 | * @param appPath Path to the application 15 | */ 16 | export const getAllAppFiles = async (appPath, filesToSkip) => { 17 | const files = []; 18 | const visited = new Set(); 19 | const traverse = async (p) => { 20 | p = await fs.realpath(p); 21 | if (filesToSkip === null || filesToSkip === void 0 ? void 0 : filesToSkip.find((e) => p.includes(e))) 22 | return; 23 | if (visited.has(p)) 24 | return; 25 | visited.add(p); 26 | const info = await fs.stat(p); 27 | if (info.isSymbolicLink()) 28 | return; 29 | if (info.isFile()) { 30 | let fileType = AppFileType.PLAIN; 31 | if (!p.endsWith('.wasm')) { 32 | const fileOutput = await spawn('file', ['--brief', '--no-pad', p]); 33 | if (p.endsWith('.asar')) { 34 | fileType = AppFileType.APP_CODE; 35 | } 36 | else if (fileOutput.startsWith(MACHO_PREFIX)) { 37 | fileType = AppFileType.MACHO; 38 | } 39 | else if (p.endsWith('.bin')) { 40 | fileType = AppFileType.SNAPSHOT; 41 | } 42 | } 43 | files.push({ 44 | relativePath: path.relative(appPath, p), 45 | type: fileType, 46 | }); 47 | } 48 | if (info.isDirectory()) { 49 | for (const child of await fs.readdir(p)) { 50 | await traverse(path.resolve(p, child)); 51 | } 52 | } 53 | }; 54 | await traverse(appPath); 55 | return files; 56 | }; 57 | //# sourceMappingURL=file-utils.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/esm/file-utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,YAAY,GAAG,SAAS,CAAC;AAE/B,MAAM,CAAN,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,+CAAK,CAAA;IACL,+CAAK,CAAA;IACL,qDAAQ,CAAA;IACR,qDAAQ,CAAA;AACV,CAAC,EALW,WAAW,KAAX,WAAW,QAKtB;AAOD;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,OAAe,EACf,WAA2B,EACP,EAAE;IACtB,MAAM,KAAK,GAAc,EAAE,CAAC;IAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAS,EAAE,EAAE;QACnC,CAAC,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAG,OAAO;QACpD,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO;QAC3B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAEf,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,cAAc,EAAE;YAAE,OAAO;QAClC,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE;YACjB,IAAI,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC;YAEjC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;gBACxB,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;gBACnE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;oBACvB,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;iBACjC;qBAAM,IAAI,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;oBAC9C,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC;iBAC9B;qBAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;oBAC7B,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;iBACjC;aACF;YAED,KAAK,CAAC,IAAI,CAAC;gBACT,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;gBACvC,IAAI,EAAE,QAAQ;aACf,CAAC,CAAC;SACJ;QAED,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;YACtB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;gBACvC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;aACxC;SACF;IACH,CAAC,CAAC;IACF,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAExB,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"} -------------------------------------------------------------------------------- /src/build/universal/dist/esm/index.d.ts: -------------------------------------------------------------------------------- 1 | declare type MakeUniversalOpts = { 2 | /** 3 | * Absolute file system path to the x64 version of your application. E.g. /Foo/bar/MyApp_x64.app 4 | */ 5 | x64AppPath: string; 6 | /** 7 | * Absolute file system path to the arm64 version of your application. E.g. /Foo/bar/MyApp_arm64.app 8 | */ 9 | arm64AppPath: string; 10 | x64AsarPath?: string; 11 | arm64AsarPath?: string; 12 | filesToSkip?: Array; 13 | /** 14 | * Absolute file system path you want the universal app to be written to. E.g. /Foo/var/MyApp_universal.app 15 | * 16 | * If this file exists it will be overwritten ONLY if "force" is set to true 17 | */ 18 | outAppPath: string; 19 | /** 20 | * Forcefully overwrite any existing files that are in the way of generating the universal application 21 | */ 22 | force: boolean; 23 | }; 24 | export declare const makeUniversalApp: (opts: MakeUniversalOpts) => Promise; 25 | export {}; 26 | -------------------------------------------------------------------------------- /src/build/universal/dist/esm/index.js: -------------------------------------------------------------------------------- 1 | import { spawn } from '@malept/cross-spawn-promise'; 2 | import * as asar from 'asar'; 3 | import * as fs from 'fs-extra'; 4 | import * as os from 'os'; 5 | import * as path from 'path'; 6 | import * as dircompare from 'dir-compare'; 7 | import { AppFileType, getAllAppFiles } from './file-utils'; 8 | import { AsarMode, detectAsarMode } from './asar-utils'; 9 | import { sha } from './sha'; 10 | import { d } from './debug'; 11 | const dupedFiles = (files) => files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE); 12 | export const makeUniversalApp = async (opts) => { 13 | var _a, _b; 14 | d('making a universal app with options', opts); 15 | if (process.platform !== 'darwin') 16 | throw new Error('@electron/universal is only supported on darwin platforms'); 17 | if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath)) 18 | throw new Error('Expected opts.x64AppPath to be an absolute path but it was not'); 19 | if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath)) 20 | throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not'); 21 | if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath)) 22 | throw new Error('Expected opts.outAppPath to be an absolute path but it was not'); 23 | if (await fs.pathExists(opts.outAppPath)) { 24 | d('output path exists already'); 25 | if (!opts.force) { 26 | throw new Error(`The out path "${opts.outAppPath}" already exists and force is not set to true`); 27 | } 28 | else { 29 | d('overwriting existing application because force == true'); 30 | await fs.remove(opts.outAppPath); 31 | } 32 | } 33 | const x64AsarMode = await detectAsarMode(opts.x64AppPath, opts.x64AsarPath); 34 | const arm64AsarMode = await detectAsarMode(opts.arm64AppPath, opts.arm64AsarPath); 35 | d('detected x64AsarMode =', x64AsarMode); 36 | d('detected arm64AsarMode =', arm64AsarMode); 37 | if (x64AsarMode !== arm64AsarMode) 38 | throw new Error('Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)'); 39 | const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-')); 40 | d('building universal app in', tmpDir); 41 | try { 42 | d('copying x64 app as starter template'); 43 | const tmpApp = path.resolve(tmpDir, 'Tmp.app'); 44 | await spawn('cp', ['-R', opts.x64AppPath, tmpApp]); 45 | const uniqueToX64 = []; 46 | const uniqueToArm64 = []; 47 | const x64Files = await getAllAppFiles(await fs.realpath(tmpApp), opts.filesToSkip); 48 | const arm64Files = await getAllAppFiles(await fs.realpath(opts.arm64AppPath), opts.filesToSkip); 49 | for (const file of dupedFiles(x64Files)) { 50 | if (!arm64Files.some((f) => f.relativePath === file.relativePath)) 51 | uniqueToX64.push(file.relativePath); 52 | } 53 | for (const file of dupedFiles(arm64Files)) { 54 | if (!x64Files.some((f) => f.relativePath === file.relativePath)) 55 | uniqueToArm64.push(file.relativePath); 56 | } 57 | if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) { 58 | d('some files were not in both builds, aborting'); 59 | console.error({ 60 | uniqueToX64, 61 | uniqueToArm64, 62 | }); 63 | throw new Error('While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds'); 64 | } 65 | for (const file of x64Files.filter((f) => f.type === AppFileType.PLAIN)) { 66 | const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath)); 67 | const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath)); 68 | if (x64Sha !== arm64Sha) { 69 | d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`); 70 | throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`); 71 | } 72 | } 73 | for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) { 74 | const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)); 75 | const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath)); 76 | d('joining two MachO files with lipo', { 77 | first, 78 | second, 79 | }); 80 | await spawn('lipo', [ 81 | first, 82 | second, 83 | '-create', 84 | '-output', 85 | await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)), 86 | ]); 87 | } 88 | /** 89 | * If we don't have an ASAR we need to check if the two "app" folders are identical, if 90 | * they are then we can just leave one there and call it a day. If the app folders for x64 91 | * and arm64 are different though we need to rename each folder and create a new fake "app" 92 | * entrypoint to dynamically load the correct app folder 93 | */ 94 | if (x64AsarMode === AsarMode.NO_ASAR) { 95 | d('checking if the x64 and arm64 app folders are identical'); 96 | const comparison = await dircompare.compare(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), { compareSize: true, compareContent: true }); 97 | if (!comparison.same) { 98 | d('x64 and arm64 app folders are different, creating dynamic entry ASAR'); 99 | await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64')); 100 | await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64')); 101 | const entryAsar = path.resolve(tmpDir, 'entry-asar'); 102 | await fs.mkdir(entryAsar); 103 | await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), path.resolve(entryAsar, 'index.js')); 104 | let pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json')); 105 | pj.main = 'index.js'; 106 | await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); 107 | await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar')); 108 | } 109 | else { 110 | d('x64 and arm64 app folders are the same'); 111 | } 112 | } 113 | /** 114 | * If we have an ASAR we just need to check if the two "app.asar" files have the same hash, 115 | * if they are, same as above, we can leave one there and call it a day. If they're different 116 | * we have to make a dynamic entrypoint. There is an assumption made here that every file in 117 | * app.asar.unpacked is a native node module. This assumption _may_ not be true so we should 118 | * look at codifying that assumption as actual logic. 119 | */ 120 | // FIXME: Codify the assumption that app.asar.unpacked only contains native modules 121 | if (x64AsarMode === AsarMode.HAS_ASAR) { 122 | d('checking if the x64 and arm64 asars are identical'); 123 | const x64AsarSha = await sha((_a = opts.x64AsarPath) !== null && _a !== void 0 ? _a : path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar')); 124 | const arm64AsarSha = await sha((_b = opts.arm64AsarPath) !== null && _b !== void 0 ? _b : path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar')); 125 | if (x64AsarSha !== arm64AsarSha) { 126 | d('x64 and arm64 asars are different'); 127 | /*await fs.move( 128 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 129 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'), 130 | ); 131 | const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked'); 132 | if (await fs.pathExists(x64Unpacked)) { 133 | await fs.move( 134 | x64Unpacked, 135 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'), 136 | ); 137 | } 138 | 139 | await fs.copy( 140 | path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), 141 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar'), 142 | ); 143 | const arm64Unpacked = path.resolve( 144 | opts.arm64AppPath, 145 | 'Contents', 146 | 'Resources', 147 | 'app.asar.unpacked', 148 | ); 149 | if (await fs.pathExists(arm64Unpacked)) { 150 | await fs.copy( 151 | arm64Unpacked, 152 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'), 153 | ); 154 | } 155 | 156 | const entryAsar = path.resolve(tmpDir, 'entry-asar'); 157 | await fs.mkdir(entryAsar); 158 | await fs.copy( 159 | path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), 160 | path.resolve(entryAsar, 'index.js'), 161 | ); 162 | let pj = JSON.parse( 163 | ( 164 | await asar.extractFile( 165 | path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 166 | 'package.json', 167 | ) 168 | ).toString('utf8'), 169 | ); 170 | pj.main = 'index.js'; 171 | await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); 172 | await asar.createPackage( 173 | entryAsar, 174 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 175 | );*/ 176 | } 177 | else { 178 | d('x64 and arm64 asars are the same'); 179 | } 180 | } 181 | for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) { 182 | d('copying snapshot file', snapshotsFile.relativePath, 'to target application'); 183 | await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath)); 184 | } 185 | d('moving final universal app to target destination'); 186 | await fs.mkdirp(path.dirname(opts.outAppPath)); 187 | await spawn('mv', [tmpApp, opts.outAppPath]); 188 | } 189 | catch (err) { 190 | throw err; 191 | } 192 | finally { 193 | await fs.remove(tmpDir); 194 | } 195 | }; 196 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/esm/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/build/universal/dist/esm/sha.d.ts: -------------------------------------------------------------------------------- 1 | export declare const sha: (filePath: string) => Promise; 2 | -------------------------------------------------------------------------------- /src/build/universal/dist/esm/sha.js: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as crypto from 'crypto'; 3 | import { d } from './debug'; 4 | export const sha = async (filePath) => { 5 | d('hashing', filePath); 6 | const hash = crypto.createHash('sha256'); 7 | hash.setEncoding('hex'); 8 | const fileStream = fs.createReadStream(filePath); 9 | fileStream.pipe(hash); 10 | await new Promise((resolve, reject) => { 11 | fileStream.on('end', () => resolve()); 12 | fileStream.on('error', (err) => reject(err)); 13 | }); 14 | return hash.read(); 15 | }; 16 | //# sourceMappingURL=sha.js.map -------------------------------------------------------------------------------- /src/build/universal/dist/esm/sha.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sha.js","sourceRoot":"","sources":["../../src/sha.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,SAAS,CAAC;AAE5B,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5C,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACjD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACtC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"} -------------------------------------------------------------------------------- /src/build/universal/entry-asar/has-asar.js: -------------------------------------------------------------------------------- 1 | if (process.arch === 'arm64') { 2 | process._archPath = require.resolve('../app-arm64.asar'); 3 | } else { 4 | process._archPath = require.resolve('../app-x64.asar'); 5 | } 6 | 7 | require(process._archPath); 8 | -------------------------------------------------------------------------------- /src/build/universal/entry-asar/no-asar.js: -------------------------------------------------------------------------------- 1 | if (process.arch === 'arm64') { 2 | process._archPath = require.resolve('../app-arm64'); 3 | } else { 4 | process._archPath = require.resolve('../app-x64'); 5 | } 6 | 7 | require(process._archPath); 8 | -------------------------------------------------------------------------------- /src/build/universal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-universal", 3 | "version": "0.0.2", 4 | "description": "Utility for creating Universal macOS applications from two x64 and arm64 Electron applications", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "license": "MIT", 8 | "keywords": [ 9 | "electron", 10 | "apple silicon", 11 | "universal" 12 | ], 13 | "engines": { 14 | "node": ">=8.6" 15 | }, 16 | "files": [ 17 | "dist/*", 18 | "entry-asar/*", 19 | "README.md" 20 | ], 21 | "author": "Samuel Attard", 22 | "scripts": { 23 | "postinstall": "yarn build", 24 | "build": "tsc && tsc -p tsconfig.esm.json", 25 | "lint": "prettier --check \"src/**/*.ts\"", 26 | "prepublishOnly": "npm run build", 27 | "test": "exit 0" 28 | }, 29 | "devDependencies": { 30 | "@continuous-auth/semantic-release-npm": "^2.0.0", 31 | "@types/node": "^14.17.6", 32 | "husky": "^4.3.0", 33 | "lint-staged": "^10.5.1", 34 | "prettier": "^2.1.2", 35 | "semantic-release": "^17.2.2" 36 | }, 37 | "dependencies": { 38 | "@malept/cross-spawn-promise": "^1.1.0", 39 | "@types/debug": "^4.1.5", 40 | "@types/fs-extra": "^9.0.6", 41 | "asar": "^3.0.3", 42 | "debug": "^4.3.1", 43 | "dir-compare": "^2.4.0", 44 | "fs-extra": "^9.0.1", 45 | "typescript": "^4.1.3" 46 | }, 47 | "husky": { 48 | "hooks": { 49 | "pre-commit": "lint-staged" 50 | } 51 | }, 52 | "lint-staged": { 53 | "*.ts": [ 54 | "prettier --write" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/build/universal/src/asar-utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as path from 'path'; 3 | import { d } from './debug'; 4 | 5 | export enum AsarMode { 6 | NO_ASAR, 7 | HAS_ASAR, 8 | } 9 | 10 | export const detectAsarMode = async (appPath: string, asarPath?: string) => { 11 | d('checking asar mode of', appPath); 12 | const result = asarPath ?? path.resolve(appPath, 'Contents', 'Resources', 'app.asar'); 13 | 14 | if (!(await fs.pathExists(result))) { 15 | d('determined no asar'); 16 | return AsarMode.NO_ASAR; 17 | } 18 | 19 | d('determined has asar'); 20 | return AsarMode.HAS_ASAR; 21 | }; 22 | -------------------------------------------------------------------------------- /src/build/universal/src/debug.ts: -------------------------------------------------------------------------------- 1 | import * as debug from 'debug'; 2 | 3 | export const d = debug('electron-universal'); 4 | -------------------------------------------------------------------------------- /src/build/universal/src/file-utils.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from '@malept/cross-spawn-promise'; 2 | import * as fs from 'fs-extra'; 3 | import * as path from 'path'; 4 | 5 | const MACHO_PREFIX = 'Mach-O '; 6 | 7 | export enum AppFileType { 8 | MACHO, 9 | PLAIN, 10 | SNAPSHOT, 11 | APP_CODE, 12 | } 13 | 14 | export type AppFile = { 15 | relativePath: string; 16 | type: AppFileType; 17 | }; 18 | 19 | /** 20 | * 21 | * @param appPath Path to the application 22 | */ 23 | export const getAllAppFiles = async ( 24 | appPath: string, 25 | filesToSkip?: Array, 26 | ): Promise => { 27 | const files: AppFile[] = []; 28 | 29 | const visited = new Set(); 30 | const traverse = async (p: string) => { 31 | p = await fs.realpath(p); 32 | if (filesToSkip?.find((e) => p.includes(e))) return; 33 | if (visited.has(p)) return; 34 | visited.add(p); 35 | 36 | const info = await fs.stat(p); 37 | if (info.isSymbolicLink()) return; 38 | if (info.isFile()) { 39 | let fileType = AppFileType.PLAIN; 40 | 41 | if (!p.endsWith('.wasm')) { 42 | const fileOutput = await spawn('file', ['--brief', '--no-pad', p]); 43 | if (p.endsWith('.asar')) { 44 | fileType = AppFileType.APP_CODE; 45 | } else if (fileOutput.startsWith(MACHO_PREFIX)) { 46 | fileType = AppFileType.MACHO; 47 | } else if (p.endsWith('.bin')) { 48 | fileType = AppFileType.SNAPSHOT; 49 | } 50 | } 51 | 52 | files.push({ 53 | relativePath: path.relative(appPath, p), 54 | type: fileType, 55 | }); 56 | } 57 | 58 | if (info.isDirectory()) { 59 | for (const child of await fs.readdir(p)) { 60 | await traverse(path.resolve(p, child)); 61 | } 62 | } 63 | }; 64 | await traverse(appPath); 65 | 66 | return files; 67 | }; 68 | -------------------------------------------------------------------------------- /src/build/universal/src/index.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from '@malept/cross-spawn-promise'; 2 | import * as asar from 'asar'; 3 | import * as fs from 'fs-extra'; 4 | import * as os from 'os'; 5 | import * as path from 'path'; 6 | import * as dircompare from 'dir-compare'; 7 | import { AppFile, AppFileType, getAllAppFiles } from './file-utils'; 8 | import { AsarMode, detectAsarMode } from './asar-utils'; 9 | import { sha } from './sha'; 10 | import { d } from './debug'; 11 | 12 | type MakeUniversalOpts = { 13 | /** 14 | * Absolute file system path to the x64 version of your application. E.g. /Foo/bar/MyApp_x64.app 15 | */ 16 | x64AppPath: string; 17 | /** 18 | * Absolute file system path to the arm64 version of your application. E.g. /Foo/bar/MyApp_arm64.app 19 | */ 20 | arm64AppPath: string; 21 | 22 | x64AsarPath?: string; 23 | arm64AsarPath?: string; 24 | filesToSkip?: Array; 25 | /** 26 | * Absolute file system path you want the universal app to be written to. E.g. /Foo/var/MyApp_universal.app 27 | * 28 | * If this file exists it will be overwritten ONLY if "force" is set to true 29 | */ 30 | outAppPath: string; 31 | /** 32 | * Forcefully overwrite any existing files that are in the way of generating the universal application 33 | */ 34 | force: boolean; 35 | }; 36 | 37 | const dupedFiles = (files: AppFile[]) => 38 | files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE); 39 | 40 | export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise => { 41 | d('making a universal app with options', opts); 42 | 43 | if (process.platform !== 'darwin') 44 | throw new Error('@electron/universal is only supported on darwin platforms'); 45 | if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath)) 46 | throw new Error('Expected opts.x64AppPath to be an absolute path but it was not'); 47 | if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath)) 48 | throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not'); 49 | if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath)) 50 | throw new Error('Expected opts.outAppPath to be an absolute path but it was not'); 51 | 52 | if (await fs.pathExists(opts.outAppPath)) { 53 | d('output path exists already'); 54 | if (!opts.force) { 55 | throw new Error( 56 | `The out path "${opts.outAppPath}" already exists and force is not set to true`, 57 | ); 58 | } else { 59 | d('overwriting existing application because force == true'); 60 | await fs.remove(opts.outAppPath); 61 | } 62 | } 63 | 64 | const x64AsarMode = await detectAsarMode(opts.x64AppPath, opts.x64AsarPath); 65 | const arm64AsarMode = await detectAsarMode(opts.arm64AppPath, opts.arm64AsarPath); 66 | d('detected x64AsarMode =', x64AsarMode); 67 | d('detected arm64AsarMode =', arm64AsarMode); 68 | 69 | if (x64AsarMode !== arm64AsarMode) 70 | throw new Error( 71 | 'Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)', 72 | ); 73 | 74 | const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-')); 75 | d('building universal app in', tmpDir); 76 | 77 | try { 78 | d('copying x64 app as starter template'); 79 | const tmpApp = path.resolve(tmpDir, 'Tmp.app'); 80 | await spawn('cp', ['-R', opts.x64AppPath, tmpApp]); 81 | 82 | const uniqueToX64: string[] = []; 83 | const uniqueToArm64: string[] = []; 84 | const x64Files = await getAllAppFiles(await fs.realpath(tmpApp), opts.filesToSkip); 85 | const arm64Files = await getAllAppFiles(await fs.realpath(opts.arm64AppPath), opts.filesToSkip); 86 | 87 | for (const file of dupedFiles(x64Files)) { 88 | if (!arm64Files.some((f) => f.relativePath === file.relativePath)) 89 | uniqueToX64.push(file.relativePath); 90 | } 91 | for (const file of dupedFiles(arm64Files)) { 92 | if (!x64Files.some((f) => f.relativePath === file.relativePath)) 93 | uniqueToArm64.push(file.relativePath); 94 | } 95 | if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) { 96 | d('some files were not in both builds, aborting'); 97 | console.error({ 98 | uniqueToX64, 99 | uniqueToArm64, 100 | }); 101 | throw new Error( 102 | 'While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds', 103 | ); 104 | } 105 | 106 | for (const file of x64Files.filter((f) => f.type === AppFileType.PLAIN)) { 107 | const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath)); 108 | const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath)); 109 | if (x64Sha !== arm64Sha) { 110 | d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`); 111 | throw new Error( 112 | `Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`, 113 | ); 114 | } 115 | } 116 | 117 | for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) { 118 | const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)); 119 | const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath)); 120 | 121 | d('joining two MachO files with lipo', { 122 | first, 123 | second, 124 | }); 125 | await spawn('lipo', [ 126 | first, 127 | second, 128 | '-create', 129 | '-output', 130 | await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)), 131 | ]); 132 | } 133 | 134 | /** 135 | * If we don't have an ASAR we need to check if the two "app" folders are identical, if 136 | * they are then we can just leave one there and call it a day. If the app folders for x64 137 | * and arm64 are different though we need to rename each folder and create a new fake "app" 138 | * entrypoint to dynamically load the correct app folder 139 | */ 140 | if (x64AsarMode === AsarMode.NO_ASAR) { 141 | d('checking if the x64 and arm64 app folders are identical'); 142 | const comparison = await dircompare.compare( 143 | path.resolve(tmpApp, 'Contents', 'Resources', 'app'), 144 | path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), 145 | { compareSize: true, compareContent: true }, 146 | ); 147 | 148 | if (!comparison.same) { 149 | d('x64 and arm64 app folders are different, creating dynamic entry ASAR'); 150 | await fs.move( 151 | path.resolve(tmpApp, 'Contents', 'Resources', 'app'), 152 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'), 153 | ); 154 | await fs.copy( 155 | path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), 156 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'), 157 | ); 158 | 159 | const entryAsar = path.resolve(tmpDir, 'entry-asar'); 160 | await fs.mkdir(entryAsar); 161 | await fs.copy( 162 | path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), 163 | path.resolve(entryAsar, 'index.js'), 164 | ); 165 | let pj = await fs.readJson( 166 | path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'), 167 | ); 168 | pj.main = 'index.js'; 169 | await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); 170 | await asar.createPackage( 171 | entryAsar, 172 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 173 | ); 174 | } else { 175 | d('x64 and arm64 app folders are the same'); 176 | } 177 | } 178 | 179 | /** 180 | * If we have an ASAR we just need to check if the two "app.asar" files have the same hash, 181 | * if they are, same as above, we can leave one there and call it a day. If they're different 182 | * we have to make a dynamic entrypoint. There is an assumption made here that every file in 183 | * app.asar.unpacked is a native node module. This assumption _may_ not be true so we should 184 | * look at codifying that assumption as actual logic. 185 | */ 186 | // FIXME: Codify the assumption that app.asar.unpacked only contains native modules 187 | if (x64AsarMode === AsarMode.HAS_ASAR) { 188 | d('checking if the x64 and arm64 asars are identical'); 189 | const x64AsarSha = await sha( 190 | opts.x64AsarPath ?? path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 191 | ); 192 | const arm64AsarSha = await sha( 193 | opts.arm64AsarPath ?? path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), 194 | ); 195 | 196 | if (x64AsarSha !== arm64AsarSha) { 197 | d('x64 and arm64 asars are different'); 198 | /*await fs.move( 199 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 200 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'), 201 | ); 202 | const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked'); 203 | if (await fs.pathExists(x64Unpacked)) { 204 | await fs.move( 205 | x64Unpacked, 206 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'), 207 | ); 208 | } 209 | 210 | await fs.copy( 211 | path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), 212 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar'), 213 | ); 214 | const arm64Unpacked = path.resolve( 215 | opts.arm64AppPath, 216 | 'Contents', 217 | 'Resources', 218 | 'app.asar.unpacked', 219 | ); 220 | if (await fs.pathExists(arm64Unpacked)) { 221 | await fs.copy( 222 | arm64Unpacked, 223 | path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'), 224 | ); 225 | } 226 | 227 | const entryAsar = path.resolve(tmpDir, 'entry-asar'); 228 | await fs.mkdir(entryAsar); 229 | await fs.copy( 230 | path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), 231 | path.resolve(entryAsar, 'index.js'), 232 | ); 233 | let pj = JSON.parse( 234 | ( 235 | await asar.extractFile( 236 | path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 237 | 'package.json', 238 | ) 239 | ).toString('utf8'), 240 | ); 241 | pj.main = 'index.js'; 242 | await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); 243 | await asar.createPackage( 244 | entryAsar, 245 | path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), 246 | );*/ 247 | } else { 248 | d('x64 and arm64 asars are the same'); 249 | } 250 | } 251 | 252 | for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) { 253 | d('copying snapshot file', snapshotsFile.relativePath, 'to target application'); 254 | await fs.copy( 255 | path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), 256 | path.resolve(tmpApp, snapshotsFile.relativePath), 257 | ); 258 | } 259 | 260 | d('moving final universal app to target destination'); 261 | await fs.mkdirp(path.dirname(opts.outAppPath)); 262 | await spawn('mv', [tmpApp, opts.outAppPath]); 263 | } catch (err) { 264 | throw err; 265 | } finally { 266 | await fs.remove(tmpDir); 267 | } 268 | }; 269 | -------------------------------------------------------------------------------- /src/build/universal/src/sha.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as crypto from 'crypto'; 3 | import { d } from './debug'; 4 | 5 | export const sha = async (filePath: string) => { 6 | d('hashing', filePath); 7 | const hash = crypto.createHash('sha256'); 8 | hash.setEncoding('hex'); 9 | const fileStream = fs.createReadStream(filePath); 10 | fileStream.pipe(hash); 11 | await new Promise((resolve, reject) => { 12 | fileStream.on('end', () => resolve()); 13 | fileStream.on('error', (err) => reject(err)); 14 | }); 15 | return hash.read(); 16 | }; 17 | -------------------------------------------------------------------------------- /src/build/universal/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "outDir": "dist/esm" 6 | } 7 | } -------------------------------------------------------------------------------- /src/build/universal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "lib": [ 6 | "es2017" 7 | ], 8 | "sourceMap": true, 9 | "strict": true, 10 | "outDir": "dist/cjs", 11 | "types": [ 12 | "node", 13 | ], 14 | "allowSyntheticDefaultImports": true, 15 | "moduleResolution": "node", 16 | "declaration": true 17 | }, 18 | "include": [ 19 | "src" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dev: { 3 | // logs otp results to console 4 | debug: false 5 | }, 6 | build: { 7 | setApp: false, 8 | universal: false 9 | }, 10 | shortcuts: { 11 | resync_and_copy: "CommandOrControl+Shift+E" 12 | }, 13 | imessage: { 14 | max_connection_attempts_per_session: 10, 15 | connection_wait_ms: 6000 16 | }, 17 | overlay: { 18 | offset: { 19 | x: 10, 20 | y: 80 21 | } 22 | }, 23 | timeouts: { 24 | tray_title_ms: 800, 25 | }, 26 | text: { 27 | unknown_string: "Unknown", 28 | connected_string: "🟢 Connected to iMessage", 29 | error_string: "⚠️ Setup Ohtipi", 30 | update_available: "⏳ Update Available", 31 | update_downloaded: "⌛️ Update Downloaded, Restart App", 32 | download_progress: "⏳ Downloading Update:", 33 | recent_label: "Recent", 34 | open_at_login_label: "Open at Login", 35 | open_at_login_tooltip: "Open Ohtipi automatically when you power on your Mac", 36 | quit_label: "Quit Ohtipi", 37 | history_item_template: " - ", 38 | overlay_subtitle: "Copied to Clipboard", 39 | resync: "Resync && Copy", // needs to escape ampersand with second ampersand (not a typo here) 40 | disable_shortcuts: "Keyboard Shortcuts", 41 | disable_shortcuts_tooltip: "Disable keyboard shortcuts if Ohtipi uses the same keyboard shortcuts as another app", 42 | resync_and_copy_tooltip: "Sometimes iMessage likes to sleep on the job. If OhTipi ever misses a message, use this option to sync recent messages and copy the latest code to your clipboard", 43 | nothing_to_sync: "Nothing to sync...", 44 | sync_success: "Copied!", 45 | credit_window_title: "About Ohtipi", 46 | credits: "Ohtipi was developed by Alec Armbruster, Justin Mitchell at Yac. https://ohtipi.com" 47 | } 48 | } -------------------------------------------------------------------------------- /src/libs/imessage/index.js: -------------------------------------------------------------------------------- 1 | const osa = require('osa2') 2 | const ol = require('one-liner') 3 | const assert = require('assert') 4 | const macosVersion = require('macos-version') 5 | const messagesDb = require('./lib/messages-db.js') 6 | 7 | function warn(str) { 8 | if (!process.env.SUPPRESS_OSA_IMESSAGE_WARNINGS) { 9 | console.error(ol(str)) 10 | } 11 | } 12 | 13 | // Instead of doing something reasonable, Apple stores dates as the number of 14 | // seconds since 01-01-2001 00:00:00 GMT. DATE_OFFSET is the offset in seconds 15 | // between their epoch and unix time 16 | const DATE_OFFSET = 978307200 17 | 18 | // Gets the current Apple-style timestamp 19 | function appleTimeNow() { 20 | return Math.floor(Date.now() / 1000) - DATE_OFFSET 21 | } 22 | 23 | // Transforms an Apple-style timestamp to a proper unix timestamp 24 | function fromAppleTime(ts) { 25 | if (ts == 0) { 26 | return null 27 | } 28 | 29 | // unpackTime returns 0 if the timestamp wasn't packed 30 | // TODO: see `packTimeConditionally`'s comment 31 | if (unpackTime(ts) != 0) { 32 | ts = unpackTime(ts) 33 | } 34 | 35 | return new Date((ts + DATE_OFFSET) * 1000) 36 | } 37 | 38 | // Since macOS 10.13 High Sierra, some timestamps appear to have extra data 39 | // packed. Dividing by 10^9 seems to get an Apple-style timestamp back. 40 | // According to a StackOverflow user, timestamps now have nanosecond precision 41 | function unpackTime(ts) { 42 | return Math.floor(ts / Math.pow(10, 9)) 43 | } 44 | 45 | // TODO: Do some kind of database-based detection rather than relying on the 46 | // operating system version 47 | function packTimeConditionally(ts) { 48 | if (macosVersion.is('>=10.13')) { 49 | return ts * Math.pow(10, 9) 50 | } else { 51 | return ts 52 | } 53 | } 54 | 55 | // Gets the proper handle string for a contact with the given name 56 | function handleForName(name) { 57 | assert(typeof name == 'string', 'name must be a string') 58 | return osa(name => { 59 | const Messages = Application('Messages') 60 | return Messages.buddies.whose({ 61 | name: name 62 | })[0].handle() 63 | })(name) 64 | } 65 | 66 | // Gets the display name for a given handle 67 | // TODO: support group chats 68 | function nameForHandle(handle) { 69 | assert(typeof handle == 'string', 'handle must be a string') 70 | return osa(handle => { 71 | const Messages = Application('Messages') 72 | return Messages.buddies.whose({ 73 | handle: handle 74 | }).name()[0] 75 | })(handle) 76 | } 77 | 78 | // Sends a message to the given handle 79 | function send(handle, message) { 80 | assert(typeof handle == 'string', 'handle must be a string') 81 | assert(typeof message == 'string', 'message must be a string') 82 | return osa((handle, message) => { 83 | const Messages = Application('Messages') 84 | 85 | let target 86 | 87 | try { 88 | target = Messages.buddies.whose({ 89 | handle: handle 90 | })[0] 91 | } catch (e) {} 92 | 93 | try { 94 | target = Messages.textChats.byId('iMessage;+;' + handle)() 95 | } catch (e) {} 96 | 97 | try { 98 | Messages.send(message, { 99 | to: target 100 | }) 101 | } catch (e) { 102 | throw new Error(`no thread with handle '${handle}'`) 103 | } 104 | })(handle, message) 105 | } 106 | 107 | let emitter = null 108 | let emittedMsgs = [] 109 | 110 | function listen() { 111 | // If listen has already been run, return the existing emitter 112 | if (emitter != null) { 113 | return emitter 114 | } 115 | 116 | // Create an EventEmitter 117 | emitter = new(require('events')).EventEmitter() 118 | 119 | let last = packTimeConditionally(appleTimeNow() - 5) 120 | let bail = false 121 | 122 | const dbPromise = messagesDb.open() 123 | 124 | async function check() { 125 | const db = await dbPromise 126 | const query = ` 127 | SELECT 128 | guid, 129 | id as handle, 130 | text, 131 | date, 132 | date_read, 133 | is_from_me, 134 | cache_roomnames 135 | FROM message 136 | LEFT OUTER JOIN handle ON message.handle_id = handle.ROWID 137 | WHERE date >= ${last} 138 | ` 139 | last = packTimeConditionally(appleTimeNow()) 140 | 141 | try { 142 | const messages = await db.all(query) 143 | messages.forEach(msg => { 144 | if (emittedMsgs[msg.guid]) return 145 | emittedMsgs[msg.guid] = true 146 | emitter.emit('message', { 147 | guid: msg.guid, 148 | text: msg.text, 149 | handle: msg.handle, 150 | group: msg.cache_roomnames, 151 | fromMe: !!msg.is_from_me, 152 | date: fromAppleTime(msg.date), 153 | dateRead: fromAppleTime(msg.date_read), 154 | }) 155 | }) 156 | setTimeout(check, 1000) 157 | } catch (err) { 158 | bail = true 159 | emitter.emit('error', err) 160 | warn(`sqlite returned an error while polling for new messages! 161 | bailing out of poll routine for safety. new messages will 162 | not be detected`) 163 | } 164 | } 165 | 166 | if (bail) { 167 | return 168 | } 169 | check() 170 | return emitter 171 | } 172 | 173 | 174 | 175 | async function getRecentChats(limit = 10) { 176 | const db = await messagesDb.open() 177 | 178 | const query = ` 179 | SELECT 180 | guid as id, 181 | chat_identifier as recipientId, 182 | service_name as serviceName, 183 | room_name as roomName, 184 | display_name as displayName, 185 | FROM chat 186 | JOIN chat_handle_join ON chat_handle_join.chat_id = chat.ROWID 187 | JOIN handle ON handle.ROWID = chat_handle_join.handle_id 188 | ORDER BY handle.rowid DESC 189 | LIMIT ${limit}; 190 | ` 191 | 192 | const chats = await db.all(query); 193 | return chats 194 | } 195 | 196 | async function getRecentMessages() { 197 | const db = await messagesDb.open() 198 | 199 | // offset is in seconds 200 | let offset = 60 * 15 201 | let last = packTimeConditionally(appleTimeNow() - offset) 202 | 203 | const query = ` 204 | SELECT 205 | guid, 206 | id as handle, 207 | text, 208 | date, 209 | date_read, 210 | is_from_me, 211 | cache_roomnames 212 | FROM message 213 | LEFT OUTER JOIN handle ON message.handle_id = handle.ROWID 214 | WHERE date > ${last} 215 | LIMIT 6; 216 | ` 217 | 218 | const chats = await db.all(query); 219 | let formattedChats = []; 220 | chats.forEach(msg => { 221 | formattedChats.push({ 222 | guid: msg.guid, 223 | text: msg.text, 224 | handle: msg.handle, 225 | group: msg.cache_roomnames, 226 | fromMe: !!msg.is_from_me, 227 | date: fromAppleTime(msg.date), 228 | dateRead: fromAppleTime(msg.date_read), 229 | }) 230 | }); 231 | return formattedChats 232 | } 233 | 234 | module.exports = { 235 | send, 236 | listen, 237 | handleForName, 238 | nameForHandle, 239 | getRecentChats, 240 | SUPPRESS_WARNINGS: false, 241 | getRecentMessages 242 | } -------------------------------------------------------------------------------- /src/libs/imessage/lib/messages-db.js: -------------------------------------------------------------------------------- 1 | const sqlite = require('sqlite') 2 | const dbPath = `${process.env.HOME}/Library/Messages/chat.db` 3 | const OPEN_READONLY = 1 4 | 5 | let db 6 | async function open() { 7 | if (db) return db 8 | db = await sqlite.open(dbPath, { mode: OPEN_READONLY }) 9 | return db 10 | } 11 | 12 | let isClosing; 13 | function cleanUp() { 14 | if (db && db.driver.open && !isClosing) { 15 | isClosing = true 16 | db.close() 17 | } 18 | } 19 | process.on('exit', cleanUp) 20 | process.on('uncaughtException', cleanUp) 21 | 22 | module.exports = { 23 | open, 24 | } 25 | -------------------------------------------------------------------------------- /src/libs/imessage/macos_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "working": [ 3 | "10.12.5", 4 | "10.13", 5 | "10.13.1", 6 | "10.12.6" 7 | ], 8 | "broken": [] 9 | } 10 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const debug = require("../../config.js").dev.debug 4 | const authWords = require('./lib/auth-words') 5 | const knownServices = require('./lib/known-services') 6 | const servicePatterns = require('./lib/service-patterns') 7 | const stopwords = require('./lib/stopwords') 8 | const interventions = require("./lib/interventions") 9 | 10 | module.exports = (message) => { 11 | const service = inferService(message) 12 | const s = message.toLowerCase() 13 | let code 14 | 15 | if (debug) { 16 | console.log({ 17 | step: 0, 18 | service: service, 19 | string: s, 20 | }) 21 | } 22 | 23 | const handler = (obj) => { 24 | if (debug) { 25 | console.log({ 26 | step: 1, 27 | matchedOnLevel: obj.level, 28 | matchedObject: obj 29 | }) 30 | } 31 | 32 | delete obj.level; // it's only included so we can debug 33 | const secondPassResult = interventions(obj, message); 34 | 35 | if (debug) { 36 | console.log({ 37 | step: 2, 38 | secondPassResult: secondPassResult, 39 | original: { 40 | obj, 41 | message 42 | } 43 | }) 44 | } 45 | 46 | return secondPassResult; 47 | } 48 | 49 | const m = message.match(/\b(g-\d{4,8})\b/i) 50 | if (m) return handler({ 51 | code: m[1], 52 | service: 'google', 53 | level: 0 54 | }) 55 | 56 | code = validateAuthCode(s, /\b(\d{4,8})\b/) 57 | if (code) return handler({ 58 | code, 59 | service, 60 | level: 1 61 | }) 62 | 63 | code = validateAuthCode(s, /\b(\d{3}[- ]\d{3})\b/) 64 | if (code) return handler({ 65 | code, 66 | service, 67 | level: 2 68 | }) 69 | 70 | code = validateAuthCode(s, /\b(\d{4,8})\b/g, { 71 | isGlobal: true 72 | }) 73 | if (code) return handler({ 74 | code, 75 | service, 76 | level: 3 77 | }) 78 | 79 | code = validateAuthCode(s, /\b(\d{3}[- ]\d{3})\b/g, { 80 | isGlobal: true 81 | }) 82 | if (code) return handler({ 83 | code, 84 | service, 85 | level: 4 86 | }) 87 | 88 | code = validateAuthCode(message, /\b([\dA-Z]{6,8})\b/g, { 89 | isGlobal: true, 90 | cleanCode: (code) => code.replace(/[^\dA-Z]/g, '').trim() 91 | }) 92 | if (code) return handler({ 93 | code, 94 | service, 95 | level: 5 96 | }) 97 | 98 | // no auth code found 99 | if (service) { 100 | return handler({ 101 | code: undefined, 102 | service, 103 | level: 6 104 | }) 105 | } 106 | } 107 | 108 | function validateAuthCode(message, pattern, opts = {}) { 109 | const { 110 | isGlobal = false, 111 | cleanCode = (code) => code.replace(/[^\d]/g, '').trim() 112 | } = opts 113 | const match = message.match(pattern) 114 | 115 | if (match) { 116 | if (isGlobal) { 117 | let i = 0 118 | 119 | do { 120 | const code = match[i] 121 | const index = message.indexOf(code) 122 | const validated = validateAuthCodeMatch(message, index, code, cleanCode) 123 | if (validated) return validated 124 | } while (++i < match.length) 125 | } else { 126 | return validateAuthCodeMatch(message, match.index, match[1], cleanCode) 127 | } 128 | } 129 | } 130 | 131 | function validateAuthCodeMatch(message, index, code, cleanCode) { 132 | if (!code || !code.length) return 133 | // check for false-positives like phone numbers 134 | if (index > 0) { 135 | const prev = message.charAt(index - 1) 136 | if (prev && (prev === "-" || prev === "/" || prev === "\\" || prev === "$")) return 137 | } 138 | 139 | if (index + code.length < message.length) { 140 | const next = message.charAt(index + code.length) 141 | // make sure next character is whitespace or ending grammar 142 | if (next && [/\s/g, ",", ".", "!", " ", ",", "。"].indexOf(next) < 1) { 143 | return; 144 | } 145 | } 146 | 147 | return cleanCode(code) 148 | } 149 | 150 | function inferService(message) { 151 | const s = message.toLowerCase() 152 | 153 | for (let i = 0; i < servicePatterns.length; ++i) { 154 | const pattern = servicePatterns[i] 155 | const match = s.match(pattern) 156 | const service = match && match[1] && match[1].trim() 157 | 158 | if (service && service !== "" && !authWords.has(service)) { 159 | // check for some false-positive cases 160 | if (stopwords.has(service)) { 161 | if (message.substr(match.index, service.length) !== service.toUpperCase()) { 162 | continue 163 | } 164 | } 165 | 166 | return service 167 | } 168 | } 169 | 170 | // fallback to checking if the message contains any names of known services 171 | for (let i = 0; i < knownServices.length; ++i) { 172 | const service = knownServices[i] 173 | if (s.indexOf(service) >= 0) { 174 | return service 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/lib/auth-words.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = new Set([ 4 | 'your', 5 | 'auth', 6 | 'login', 7 | 'activation', 8 | 'authentication', 9 | 'verification', 10 | 'confirmation', 11 | 'access code', 12 | 'code', 13 | 'pin', 14 | 'otp', 15 | 'purchase', 16 | 'receipt', 17 | 'phone', 18 | 'number', 19 | 'security', 20 | '2-step', 21 | '2-fac', 22 | '2-factor' 23 | ]) 24 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/lib/custom-filters.js: -------------------------------------------------------------------------------- 1 | const config = require("../../../config.js") 2 | 3 | module.exports = [ 4 | // user-requested data transformations 5 | // to standardize behavior across vendors 6 | 7 | // kotak bank 8 | { 9 | notes: "Kotak Bank, includes a bunch of numbers", 10 | example: "123456 is the OTP for transaction of INR 1234.00 on your Kotak Bank Card 1234 at AMAZON PAY INDIA PRIVATET valid for 15 mins. DONT SHARE OTP WITH ANYONE INCLUDING BANK OFFICIALS.", 11 | ifServiceMatches: "transaction", 12 | ensure(messageText) { 13 | try { 14 | return messageText.includes("Kotak Bank") && messageText.split(" ").length > 5 && Number.isInteger(Number(messageText.split(" ")[0])) 15 | } catch (e) { 16 | return false; 17 | } 18 | }, 19 | handler(messageText) { 20 | return { 21 | code: messageText.split(" ")[0], 22 | service: "kotak bank" 23 | } 24 | } 25 | }, 26 | 27 | // google authenticator 28 | { 29 | notes: "Google Authenticator, provides G-, when user only desires to paste ", 30 | example: "G-412157 is your Google verification code.", 31 | ifServiceMatches: "google", 32 | ensure(messageText) { 33 | try { 34 | return messageText.includes("G-") && !Number.isNaN(messageText.split("G-")[1].substring(0, 6)) 35 | } catch (e) { 36 | return false; 37 | } 38 | }, 39 | handler(messageText) { 40 | return { 41 | code: messageText.split("G-")[1].substring(0, 6), 42 | service: "google" 43 | } 44 | } 45 | }, 46 | 47 | // generics (use "unknown" as service name) 48 | { 49 | notes: "Generic catch 1 (Epic Games, possibly others)", 50 | example: "Your verification code is 732825", 51 | ifServiceMatches: "undefined", 52 | ensure(messageText) { 53 | try { 54 | return messageText && messageText.startsWith("Your verification code is") && !Number.isNaN(messageText.split(" ")[4]) 55 | } catch (e) { 56 | return false; 57 | } 58 | }, 59 | handler(messageText) { 60 | return { 61 | code: messageText.split(" ")[4], 62 | service: config.text.unknown_string 63 | } 64 | } 65 | }, 66 | { 67 | notes: "Generic catch 2", 68 | example: "Your security code: 274934. Valid for 1 minutes.", 69 | ifServiceMatches: "undefined", 70 | ensure(messageText) { 71 | try { 72 | return messageText && messageText.startsWith("Your security code:") && !Number.isNaN(messageText.split(" ")[3].replace(".", "")) 73 | } catch (e) { 74 | return false; 75 | } 76 | }, 77 | handler(messageText) { 78 | return { 79 | code: messageText.split(" ")[3].replace(".", ""), 80 | service: config.text.unknown_string 81 | } 82 | } 83 | }, 84 | 85 | // misc. unknown user reported 86 | 87 | // unknown 88 | { 89 | notes: "Portal Verification", 90 | example: "Your portal verification code is : jh7112 Msg&Data rates may apply. Reply STOP to opt-out", 91 | ifServiceMatches: "undefined", 92 | ensure(messageText) { 93 | try { 94 | return messageText.includes("portal verification") && messageText.split(" ").length > 5 && messageText.split(" ")[6] && messageText.split(" ")[6].length === 6 95 | } catch (e) { 96 | return false; 97 | } 98 | }, 99 | handler(messageText) { 100 | return { 101 | code: messageText.split(" ")[6], 102 | service: config.text.unknown_string 103 | } 104 | } 105 | }, 106 | 107 | // cater allen 108 | { 109 | notes: "Cater Allen's complex OTP", 110 | example: "OTP to MAKE A NEW PAYMENT of GBP 9.94 to 560027 & 27613445. Call us if this wasn't you. NEVER share this code, not even with Cater Allen staff 699486", 111 | ifServiceMatches: "cater allen", 112 | ensure(messageText) { 113 | try { 114 | if (!Number.isInteger(Number(messageText.split(" ")[messageText.split(" ").length - 1]))) return false; 115 | return messageText.includes("Cater Allen staff") && messageText.includes("OTP to MAKE A NEW PAYMENT") && Number.isInteger(Number(messageText.split(" ")[messageText.split(" ").length - 1])) 116 | } catch (e) { 117 | return false; 118 | } 119 | }, 120 | handler(messageText) { 121 | return { 122 | code: messageText.split(" ")[messageText.split(" ").length - 1], 123 | service: "Cater Allen" 124 | } 125 | } 126 | }, 127 | ] -------------------------------------------------------------------------------- /src/libs/parse-otp-message/lib/interventions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const rules = require("./custom-filters") 4 | 5 | module.exports = (passingResult, originalString) => { 6 | let result = passingResult; 7 | rules.forEach((rule) => { 8 | const matched = rule.ifServiceMatches.toString() === "undefined" ? "unknown" : result.service && result.service.toString() === rule.ifServiceMatches.toString(); 9 | const ensured = rule.ensure(originalString); 10 | const handlerResult = ensured ? rule.handler(originalString) : null; 11 | 12 | if (matched && ensured) { 13 | result = handlerResult; 14 | return result; 15 | } 16 | }) 17 | return result; 18 | } -------------------------------------------------------------------------------- /src/libs/parse-otp-message/lib/known-services.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = [ 4 | 'td ameritrade', 5 | 'coinbase', 6 | 'ally', 7 | 'schwab', 8 | 'id.me', 9 | 'bofa', 10 | 'dropboxing', 11 | 'wise.com', 12 | 'paypal', 13 | 'venmo', 14 | 'cash', 15 | 'segment', 16 | 'verizon', 17 | 'kotak bank', 18 | 'weibo', 19 | 'wechat', 20 | 'whatsapp', 21 | 'viber', 22 | 'snapchat', 23 | 'line', 24 | 'slack', 25 | 'signal', 26 | 'telegram', 27 | 'allo', 28 | 'kakaotalk', 29 | 'voxer', 30 | 'im+', 31 | 'skype', 32 | 'facebook', 33 | 'microsoft', 34 | 'google', 35 | 'twitter', 36 | 'instagram', 37 | 'sony', 38 | 'apple', 39 | 'ubereats', 40 | 'uber', 41 | 'lyft', 42 | 'postmates', 43 | 'doordash', 44 | 'delivery.com', 45 | 'eat24', 46 | 'foodler', 47 | 'amazon', 48 | 'tencent', 49 | 'alibaba', 50 | 'taobao', 51 | 'baidu', 52 | 'youku', 53 | 'toutaio', 54 | 'netease', 55 | 'yandex', 56 | 'uc browser', 57 | 'qq browser', 58 | 'qmenu', 59 | 'sogou', 60 | 'bbm', 61 | 'ebay', 62 | 'intel', 63 | 'cisco', 64 | 'citizen', 65 | 'oracle', 66 | 'xerox', 67 | 'ibm', 68 | 'foursquare', 69 | 'hotmail', 70 | 'outlook', 71 | 'yahoo', 72 | 'netflix', 73 | 'spotify', 74 | 'producthunt', 75 | 'nike', 76 | 'adidas', 77 | 'shopify', 78 | 'wordpress', 79 | 'yelp eats', 80 | 'yelp', 81 | 'drizly', 82 | 'eaze', 83 | 'gopuff', 84 | 'grubhub', 85 | 'seamless', 86 | 'foodpanda', 87 | 'freshdirect', 88 | 'github', 89 | 'flickr', 90 | 'etsy', 91 | 'bank of america', 92 | 'lenscrafters', 93 | 'zocdoc', 94 | 'flycleaners', 95 | 'cleanly', 96 | 'handy', 97 | 'twilio', 98 | 'kik', 99 | 'xbox', 100 | 'imo', 101 | 'kayak', 102 | 'grab', 103 | 'qq', 104 | 'moonpay', 105 | 'robinhood', 106 | 'ao retail', 107 | 'cater allen', 108 | 'apple pay', 109 | 'bill.com', 110 | 'amex', 111 | 'sia', 112 | 'fanduel', 113 | 'carta' 114 | ] 115 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/lib/service-patterns.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = [ 4 | /\bfor\s+your\s+([\w\d ]{2,64})\s+account\b/, 5 | /\bon\s+your\s+([\w\d ]{2,64})\s+account\b/, 6 | /\bas\s+your\s+([\w\d ]{2,64})\s+account\b/, 7 | /\bas\s+([\w\d ]{2,64})\s+account\b/, 8 | /\byour\s+([\w\d ]{2,64})\s+account\b/, 9 | 10 | /\byour\s+([\w\d ]{2,64})\s+verification\s+code\b/, 11 | /\byour\s+([\w\d ]{2,64})\s+verification\s+number\b/, 12 | /\byour\s+([\w\d ]{2,64})\s+verification\s+pin\b/, 13 | /\byour\s+([\w\d ]{2,64})\s+activation\s+code\b/, 14 | /\byour\s+([\w\d ]{2,64})\s+activation\s+number\b/, 15 | /\byour\s+([\w\d ]{2,64})\s+activation\s+pin\b/, 16 | /\byour\s+([\w\d ]{2,64})\s+otp\s+code\b/, 17 | /\byour\s+([\w\d ]{2,64})\s+otp\s+pin\b/, 18 | /\byour\s+([\w\d ]{2,64})\s+auth\s+pin\b/, 19 | /\byour\s+([\w\d ]{2,64})\s+auth\s+code\b/, 20 | /\byour\s+([\w\d ]{2,64})\s+authentication\s+code\b/, 21 | /\byour\s+([\w\d ]{2,64})\s+authentication\s+number\b/, 22 | /\byour\s+([\w\d ]{2,64})\s+authentication\s+pin\b/, 23 | /\byour\s+([\w\d ]{2,64})\s+security\s+code\b/, 24 | /\byour\s+([\w\d ]{2,64})\s+security\s+number\b/, 25 | /\byour\s+([\w\d ]{2,64})\s+security\s+pin\b/, 26 | /\byour\s+([\w\d ]{2,64})\s+confirmation\s+code\b/, 27 | /\byour\s+([\w\d ]{2,64})\s+confirmation\s+number\b/, 28 | /\byour\s+([\w\d ]{2,64})\s+confirmation\s+pin\b/, 29 | /\byour\s+([\w\d ]{2,64})\s+access\s+code\b/, 30 | /\byour\s+([\w\d ]{2,64})\s+access\s+number\b/, 31 | /\byour\s+([\w\d ]{2,64})\s+access\s+pin\b/, 32 | 33 | /\byour\s+verification\s+code\s+for\s+([\w\d ]{2,64})\b/, 34 | /\byour\s+verification\s+number\s+for\s+([\w\d ]{2,64})\b/, 35 | /\byour\s+verification\s+pin\s+for\s+([\w\d ]{2,64})\b/, 36 | /\byour\s+activation\s+code\s+for\s+([\w\d ]{2,64})\b/, 37 | /\byour\s+activation\s+number\s+for\s+([\w\d ]{2,64})\b/, 38 | /\byour\s+activation\s+pin\s+for\s+([\w\d ]{2,64})\b/, 39 | /\byour\s+otp\s+code\s+for\s+([\w\d ]{2,64})\b/, 40 | /\byour\s+otp\s+pin\s+for\s+([\w\d ]{2,64})\b/, 41 | /\byour\s+auth\s+code\s+for\s+([\w\d ]{2,64})\b/, 42 | /\byour\s+auth\s+pin\s+for\s+([\w\d ]{2,64})\b/, 43 | /\byour\s+authentication\s+code\s+for\s+([\w\d ]{2,64})\b/, 44 | /\byour\s+authentication\s+number\s+for\s+([\w\d ]{2,64})\b/, 45 | /\byour\s+authentication\s+pin\s+for\s+([\w\d ]{2,64})\b/, 46 | /\byour\s+security\s+code\s+for\s+([\w\d ]{2,64})\b/, 47 | /\byour\s+security\s+number\s+for\s+([\w\d ]{2,64})\b/, 48 | /\byour\s+security\s+pin\s+for\s+([\w\d ]{2,64})\b/, 49 | /\byour\s+confirmation\s+code\s+for\s+([\w\d ]{2,64})\b/, 50 | /\byour\s+confirmation\s+number\s+for\s+([\w\d ]{2,64})\b/, 51 | /\byour\s+confirmation\s+pin\s+for\s+([\w\d ]{2,64})\b/, 52 | /\byour\s+access\s+code\s+for\s+([\w\d ]{2,64})\b/, 53 | /\byour\s+access\s+number\s+for\s+([\w\d ]{2,64})\b/, 54 | /\byour\s+access\s+pin\s+for\s+([\w\d ]{2,64})\b/, 55 | 56 | /\byour\s+([\w\d]{2,64})\s+code\b/, 57 | /\byour\s+([\w\d]{2,64})\s+pin\b/, 58 | 59 | /\b([\w\d]{2,64})\s+login\s+verification\s+code\b/, 60 | /\b([\w\d]{2,64})\s+login\s+verification\s+number\b/, 61 | /\b([\w\d]{2,64})\s+login\s+verification\s+pin\b/, 62 | /\b([\w\d]{2,64})\s+login\s+activation\s+code\b/, 63 | /\b([\w\d]{2,64})\s+login\s+activation\s+number\b/, 64 | /\b([\w\d]{2,64})\s+login\s+activation\s+pin\b/, 65 | /\b([\w\d]{2,64})\s+login\s+otp\s+code\b/, 66 | /\b([\w\d]{2,64})\s+login\s+otp\s+pin\b/, 67 | /\b([\w\d]{2,64})\s+login\s+auth\s+code\b/, 68 | /\b([\w\d]{2,64})\s+login\s+auth\s+number\b/, 69 | /\b([\w\d]{2,64})\s+login\s+auth\s+pin\b/, 70 | /\b([\w\d]{2,64})\s+login\s+authentication\s+code\b/, 71 | /\b([\w\d]{2,64})\s+login\s+authentication\s+number\b/, 72 | /\b([\w\d]{2,64})\s+login\s+authentication\s+pin\b/, 73 | /\b([\w\d]{2,64})\s+login\s+security\s+code\b/, 74 | /\b([\w\d]{2,64})\s+login\s+security\s+number\b/, 75 | /\b([\w\d]{2,64})\s+login\s+security\s+pin\b/, 76 | /\b([\w\d]{2,64})\s+login\s+confirmation\s+code\b/, 77 | /\b([\w\d]{2,64})\s+login\s+confirmation\s+number\b/, 78 | /\b([\w\d]{2,64})\s+login\s+confirmation\s+pin\b/, 79 | /\b([\w\d]{2,64})\s+login\s+access\s+code\b/, 80 | /\b([\w\d]{2,64})\s+login\s+access\s+number\b/, 81 | /\b([\w\d]{2,64})\s+login\s+access\s+pin\b/, 82 | 83 | /\b([\w\d]{2,64})\s+verification\s+code\b/, 84 | /\b([\w\d]{2,64})\s+verification\s+number\b/, 85 | /\b([\w\d]{2,64})\s+verification\s+pin\b/, 86 | /\b([\w\d]{2,64})\s+activation\s+code\b/, 87 | /\b([\w\d]{2,64})\s+activation\s+number\b/, 88 | /\b([\w\d]{2,64})\s+activation\s+pin\b/, 89 | /\b([\w\d]{2,64})\s+otp\s+code\b/, 90 | /\b([\w\d]{2,64})\s+otp\s+pin\b/, 91 | /\b([\w\d]{2,64})\s+auth\s+code\b/, 92 | /\b([\w\d]{2,64})\s+auth\s+number\b/, 93 | /\b([\w\d]{2,64})\s+auth\s+pin\b/, 94 | /\b([\w\d]{2,64})\s+authentication\s+code\b/, 95 | /\b([\w\d]{2,64})\s+authentication\s+number\b/, 96 | /\b([\w\d]{2,64})\s+authentication\s+pin\b/, 97 | /\b([\w\d]{2,64})\s+security\s+code\b/, 98 | /\b([\w\d]{2,64})\s+security\s+number\b/, 99 | /\b([\w\d]{2,64})\s+security\s+pin\b/, 100 | /\b([\w\d]{2,64})\s+confirmation\s+code\b/, 101 | /\b([\w\d]{2,64})\s+confirmation\s+number\b/, 102 | /\b([\w\d]{2,64})\s+confirmation\s+pin\b/, 103 | /\b([\w\d]{2,64})\s+access\s+code\b/, 104 | /\b([\w\d]{2,64})\s+access\s+number\b/, 105 | /\b([\w\d]{2,64})\s+access\s+pin\b/, 106 | 107 | /^welcome\s+to\s+([\w\d ]{2,64})[,;.]/, 108 | /^welcome\s+to\s+([\w\d]{2,64})\b/, 109 | 110 | /^\[([^\]\d]{2,64})]/, 111 | /^\(([^)\d]{2,64})\)/, 112 | 113 | /\bcode\s+for\s+([\w\d]{3,64})\b/, 114 | /\bpin\s+for\s+([\w\d]{3,64})\b/, 115 | /\botp\s+for\s+([\w\d]{3,64})\b/, 116 | /\bnumber\s+for\s+([\w\d]{3,64})\b/, 117 | 118 | /\b([\w\d]{3,64})\s+login\s+code\b/, 119 | /\b([\w\d]{3,64})\s+login\s+number\b/, 120 | /\b([\w\d]{3,64})\s+login\s+pin\b/, 121 | 122 | /\b([\w\d]{3,64})\s+code\b/, 123 | /\b([\w\d]{3,64})\s+number\b/, 124 | /\b([\w\d]{3,64})\s+pin\b/, 125 | 126 | /【([\u4e00-\u9fa5\d\w]+)】/ 127 | ] 128 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/lib/stopwords.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stopwords = require('stopwords') 4 | 5 | module.exports = new Set(stopwords.english) 6 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/test/custom.js: -------------------------------------------------------------------------------- 1 | const parse = require('..'); 2 | 3 | const trickyStrings = [ 4 | "TD Ameritrade: Your Security Code is 931313" // example 5 | ] 6 | 7 | const test = () => { 8 | trickyStrings.forEach(str => { 9 | const result = parse(str); 10 | console.log({ 11 | message: str, 12 | result: result 13 | }) 14 | }) 15 | } 16 | 17 | test() 18 | -------------------------------------------------------------------------------- /src/libs/parse-otp-message/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('ava') 4 | 5 | const parse = require('..') 6 | 7 | const testCases = [ 8 | { 9 | message: '', 10 | result: undefined 11 | }, 12 | { 13 | message: 'G-412157 is your Google verification code.', 14 | result: { 15 | code: 'G-412157', 16 | service: 'google' 17 | } 18 | }, 19 | { 20 | message: '469538 is your verification code for your Sony Entertainment Network account.', 21 | result: { 22 | code: '469538', 23 | service: 'sony entertainment network' 24 | } 25 | }, 26 | { 27 | message: '512665 (NetEase Verification Code)', 28 | result: { 29 | code: '512665', 30 | service: 'netease' 31 | } 32 | }, 33 | { 34 | message: '2-step verification is now deactivated on your Sony Entertainment Network account.', 35 | result: { 36 | code: undefined, 37 | service: 'sony entertainment network' 38 | } 39 | }, 40 | { 41 | message: '[Alibaba Group]Your verification code is 797428', 42 | result: { 43 | code: '797428', 44 | service: 'alibaba group' 45 | } 46 | }, 47 | { 48 | message: '[HuomaoTV]code: 456291. Please complete the verification within 5 minutes. If you did not operate, please ignore this message.', 49 | result: { 50 | code: '456291', 51 | service: 'huomaotv' 52 | } 53 | }, 54 | { 55 | message: 'Auth code: 2607 Please enter this code in your app.', 56 | result: { 57 | code: '2607', 58 | service: undefined 59 | } 60 | }, 61 | { 62 | message: 'Welcome to ClickSend, for your first login you\'ll need the activation PIN: 464120', 63 | result: { 64 | code: '464120', 65 | service: 'clicksend' 66 | } 67 | }, 68 | { 69 | message: 'Here is your ofo verification code: 2226', 70 | result: { 71 | code: '2226', 72 | service: 'ofo' 73 | } 74 | }, 75 | { 76 | message: 'Use 5677 as Microsoft account security code', 77 | result: { 78 | code: '5677', 79 | service: 'microsoft' 80 | } 81 | }, 82 | { 83 | message: 'Your Google verification code is 596465', 84 | result: { 85 | code: '596465', 86 | service: 'google' 87 | } 88 | }, 89 | { 90 | 91 | message: 'Your LinkedIn verification code is 804706.', 92 | result: { 93 | code: '804706', 94 | service: 'linkedin' 95 | } 96 | }, 97 | { 98 | message: 'Your WhatsApp code is 105-876 but you can simply tap on this link to verify your device: v.whatsapp.com/105876', 99 | result: { 100 | code: '105876', 101 | service: 'whatsapp' 102 | } 103 | }, 104 | { 105 | message: 'This is your secret password for REGISTRATION. GO-JEK never asks for your password, DO NOT GIVE IT TO ANYONE. Your PASSWORD is 1099 .', 106 | result: { 107 | code: '1099', 108 | service: undefined 109 | } 110 | }, 111 | { 112 | message: 'Your confirmation code is 951417. Please enter it in the text field.', 113 | result: { 114 | code: '951417', 115 | service: undefined 116 | } 117 | }, 118 | { 119 | message: '588107 is your LIKE verification code', 120 | result: { 121 | code: '588107', 122 | service: 'like' 123 | } 124 | }, 125 | { 126 | message: 'Your one-time eBay pin is 3190', 127 | result: { 128 | code: '3190', 129 | service: 'ebay' 130 | } 131 | }, 132 | { 133 | message: 'Telegram code 65847', 134 | result: { 135 | code: '65847', 136 | service: 'telegram' 137 | } 138 | }, 139 | { 140 | message: 'Helllo', 141 | result: undefined 142 | }, 143 | { 144 | message: '858365 is your 98point6 security code.', 145 | result: { 146 | code: '858365', 147 | service: '98point6' 148 | } 149 | }, 150 | { 151 | message: '0013 is your verification code for HQ Trivia', 152 | result: { 153 | code: '0013', 154 | service: 'hq trivia' 155 | } 156 | }, 157 | { 158 | message: '750963 is your Google Voice verification code', 159 | result: { 160 | code: '750963', 161 | service: 'google voice' 162 | } 163 | }, 164 | { 165 | message: 'Пароль: 1752 (никому не говорите) Доступ к информации', 166 | result: { 167 | code: '1752', 168 | service: undefined 169 | } 170 | }, 171 | 172 | { 173 | message: 'FWD from (817) 697-4520: Mark me', 174 | result: undefined 175 | }, 176 | { 177 | message: '2715', 178 | result: { 179 | code: '2715', 180 | service: undefined 181 | } 182 | }, 183 | { 184 | message: 'Snapchat code: 481489. Do not share it or use it elsewhere!', 185 | result: { 186 | code: '481489', 187 | service: 'snapchat' 188 | } 189 | }, 190 | { 191 | message: '[#] Your Uber code: 5934 qlRnn4A1sbt', 192 | result: { 193 | code: '5934', 194 | service: 'uber' 195 | } 196 | }, 197 | { 198 | message: '128931 is your BIGO LIVE verification code', 199 | result: { 200 | code: '128931', 201 | service: 'bigo live' 202 | } 203 | }, 204 | { 205 | message: 'Humaniq code: 167-262', 206 | result: { 207 | code: '167262', 208 | service: 'humaniq' 209 | } 210 | }, 211 | { 212 | message: '373473(Weibo login verification code) This code is for user authentication, please do not send it to anyone else.', 213 | result: { 214 | code: '373473', 215 | service: 'weibo' 216 | } 217 | }, 218 | { 219 | message: '[zcool]Your verification code is 991533', 220 | result: { 221 | code: '991533', 222 | service: 'zcool' 223 | } 224 | }, 225 | { 226 | message: 'G-830829', 227 | result: { 228 | code: 'G-830829', 229 | service: 'google' 230 | } 231 | }, 232 | { 233 | message: '117740 ist dein Verifizierungscode für dein Sony Entertainment Network-Konto.', 234 | result: { 235 | code: '117740', 236 | service: 'sony' 237 | } 238 | }, 239 | { 240 | message: 'Your Lyft code is 744444', 241 | result: { 242 | code: '744444', 243 | service: 'lyft' 244 | } 245 | }, 246 | { 247 | message: 'Cash Show - 賞金クイズ の確認コードは 764972 です。', 248 | result: { 249 | code: '764972', 250 | service: undefined 251 | } 252 | }, 253 | { 254 | message: '[SwiftCall]Your verification code: 6049', 255 | result: { 256 | code: '6049', 257 | service: 'swiftcall' 258 | } 259 | }, 260 | { 261 | message: 'Your Proton verification code is: 861880', 262 | result: { 263 | code: '861880', 264 | service: 'proton' 265 | } 266 | }, 267 | { 268 | message: 'VerifyCode:736136', 269 | result: { 270 | code: '736136', 271 | service: undefined 272 | } 273 | }, 274 | { 275 | message: 'WhatsApp code 507-240', 276 | result: { 277 | code: '507240', 278 | service: 'whatsapp' 279 | } 280 | }, 281 | { 282 | message: '[EggOne]Your verification code is: 562961, valid for 10 minutes. If you are not operating, please contact us as soon as possible.', 283 | result: { 284 | code: '562961', 285 | service: 'eggone' 286 | } 287 | }, 288 | { 289 | message: '(Zalo) 8568 la ma kich hoat cua so dien thoai 13658014095. Vui long nhap ma nay vao ung dung Zalo de kich hoat tai khoan.', 290 | result: { 291 | code: '8568', 292 | service: 'zalo' 293 | } 294 | }, 295 | { 296 | message: 'You are editing the phone number information of your weibo account, the verification code is: 588397 (expire in 10 min).', 297 | result: { 298 | code: '588397', 299 | service: 'weibo' 300 | } 301 | }, 302 | { 303 | message: 'Your CloudSigma verification code for MEL is 880936', 304 | result: { 305 | code: '880936', 306 | service: 'cloudsigma' 307 | } 308 | }, 309 | { 310 | message: 'G-718356() Google .', 311 | result: { 312 | code: 'G-718356', 313 | service: 'google' 314 | } 315 | }, 316 | { 317 | message: 'G-723210(이)가 Google 인증 코드입니다.', 318 | result: { 319 | code: 'G-723210', 320 | service: 'google' 321 | } 322 | }, 323 | { 324 | 325 | message: 'You requested a secure one-time password to log in to your USCIS Account. Please enter this secure one-time password: 04352398', 326 | result: { 327 | code: '04352398', 328 | service: 'uscis' 329 | } 330 | }, 331 | { 332 | message: 'Your Stairlin verification code is 815671', 333 | result: { 334 | code: '815671', 335 | service: 'stairlin' 336 | } 337 | }, 338 | { 339 | message: 'Your Purchase Code is 39M4W', 340 | result: undefined 341 | }, 342 | { 343 | message: 'Your mCent confirmation code is: 6920', 344 | result: { 345 | code: '6920', 346 | service: 'mcent' 347 | } 348 | }, 349 | { 350 | message: 'Your Zhihu verification code is 756591.', 351 | result: { 352 | code: '756591', 353 | service: 'zhihu' 354 | } 355 | }, 356 | { 357 | message: 'Hello! Your BuzzSumo verification code is 823 815', 358 | result: { 359 | code: '823815', 360 | service: 'buzzsumo' 361 | } 362 | }, 363 | { 364 | message: 'WhatsApp code 569-485. You can also tap on this link to verify your phone: v.whatsapp.com/569485', 365 | result: { 366 | code: '569485', 367 | service: 'whatsapp' 368 | } 369 | }, 370 | { 371 | message: 'Use the code (7744) on WeChat to log in to your account. Don\'t forward the code!', 372 | result: { 373 | code: '7744', 374 | service: 'wechat' 375 | } 376 | }, 377 | { 378 | message: 'grubhub order 771332', 379 | result: { 380 | code: '771332', 381 | service: 'grubhub' 382 | } 383 | }, 384 | { 385 | message: 'Your boa code is "521992"', 386 | result: { 387 | code: '521992', 388 | service: 'boa' 389 | } 390 | }, 391 | { 392 | message: 'Your Twilio verification code is: 9508', 393 | result: { 394 | code: '9508', 395 | service: 'twilio' 396 | } 397 | }, 398 | { 399 | message: 'Your Twitter confirmation coce is 180298', 400 | result: { 401 | code: '180298', 402 | service: 'twitter' 403 | } 404 | }, 405 | { 406 | message: 'Use 003407 as your password for Facebook for iPhone.', 407 | result: { 408 | code: '003407', 409 | service: 'facebook' 410 | } 411 | }, 412 | { 413 | message: 'Reasy. Set. Get. Your new glasses are ready for pick up at LensCrafters! Stop in any time to see th enew you. Questions? 718-858-7036', 414 | result: { 415 | code: undefined, 416 | service: 'lenscrafters' 417 | } 418 | }, 419 | { 420 | message: '6635 is your Postmates verification code.', 421 | result: { 422 | code: '6635', 423 | service: 'postmates' 424 | } 425 | }, 426 | { 427 | message: '388-941-', 428 | result: undefined 429 | }, 430 | { 431 | message: '388-941-4444 your code is 333222', 432 | result: { 433 | code: '333222', 434 | service: undefined 435 | } 436 | }, 437 | { 438 | message: '+1-388-941-4444 your code is 333-222', 439 | result: { 440 | code: '333222', 441 | service: undefined 442 | } 443 | }, 444 | { 445 | message: 'Microsoft access code: 6907', 446 | result: { 447 | code: '6907', 448 | service: 'microsoft' 449 | } 450 | }, 451 | { 452 | message: `<#> Your ExampleApp code is: 123ABC78 FA+9qCX9VSu`, 453 | result: { 454 | code: '123ABC78', 455 | service: 'exampleapp' 456 | } 457 | }, 458 | { 459 | message: `验证码:805281,用于华为帐号登录。转给他人将导致华为帐号被盗和个人信息泄露,谨防诈骗。【华为】`, 460 | result: { 461 | code: '805281', 462 | service: '华为' 463 | } 464 | }, 465 | { 466 | message: `【微信支付】验证码为940816,用于商户平台安全验证,5分钟内有效。若非本人操作,请忽略此消息。`, 467 | result: { 468 | code: '940816', 469 | service: '微信支付' 470 | } 471 | }, 472 | { 473 | message: `【iSlide】验证码927134,您正在登录iSlide,若非本人操作,请勿泄露。`, 474 | result: { 475 | code: '927134', 476 | service: 'islide' 477 | } 478 | }, 479 | ] 480 | 481 | testCases.forEach((testCase) => { 482 | test(testCase.message, (t) => { 483 | const result = parse(testCase.message) 484 | t.deepEqual(result, testCase.result) 485 | }) 486 | }) 487 | -------------------------------------------------------------------------------- /src/onboarding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ohtipi 6 | 7 | 108 | 109 | 110 | 111 |
112 | 113 | 114 |
115 | 116 | 118 | 119 | 121 | 123 | 124 |
125 | 126 |
127 | 129 | 130 | 132 | 134 | 135 | 136 |
137 | 138 |
139 | 140 |
141 |
142 | 143 |
144 |

145 | Welcome to Ohtipi 146 |

147 |

148 | Thanks for downloading Ohtipi! 149 |

150 |

151 | Ohtipi is a Mac only app that takes OTP texts from iMessage and sends them to your clipboard to 152 | easily and quickly autofill. 153 |

154 | 155 |
157 |
158 | Get Started 159 |
160 |
161 |
162 | 163 |
164 |
165 | 166 |
167 |

168 | We need your permission 169 |

170 |

171 | Ohtipi will work smoothly if you
allow us these permissions: 172 |

173 |
175 |
177 |
178 |
Full Disk Access
179 |
180 | 181 |
183 |
184 | Continue 185 |
186 |
187 |
190 |
191 | Continue 192 |
193 |
194 | 195 |

196 | 197 | 199 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | {{ loaderText }} 214 | 215 |

216 | 217 |
218 | 219 |
220 |
221 | 222 |
223 |

224 | Setup is done 225 |

226 |

227 | Keep an eye out for Ohtipi in your Menu Bar 228 |

229 | 230 |
231 | 232 |
233 | 234 |
235 |
236 |
238 |
239 | Tip 240 |
241 |
242 |
243 | ⌘ + Shift + E 244 |
245 |
246 |
247 | Sometimes iMessage likes to sleep on the job. If OhTipi ever misses a message, use ⌘ + Shift + E 249 | to sync recent messages and copy the latest code to your clipboard. 250 |
251 |
252 | 253 |
255 |
256 | Restart Ohtipi 257 |
258 |
259 |
260 |
261 |
262 | 263 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /src/overlay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ohtipi 6 | 7 | 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 | 33 | 35 | 36 | 37 |
38 |
39 |

{{ line_1 }}

40 |

{{ line_2 }}

41 |
42 |
43 |
44 | 45 |
46 | 47 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ohtipi", 3 | "productName": "Ohtipi", 4 | "description": "iMessage OTP", 5 | "version": "1.0.22", 6 | "author": "Alec Armbruster ", 7 | "copyright": "© 2022, Yac Inc.", 8 | "homepage": "https://ohtipi.com", 9 | "license": "MIT", 10 | "main": "index.js", 11 | "scripts": { 12 | "postinstall": "electron-builder install-app-deps", 13 | "dev": "NODE_ENV=true electron .", 14 | "test": "ava libs/parse-otp-message/test", 15 | "release": "source .env; electron-builder --config ./build/electron.config.js", 16 | "build:setapp": "./build/build-setapp.sh", 17 | "build:mac-universal": "./build/build-universal.sh", 18 | "build:setapp-legacy": "electron-builder --config ./build/electron.config.js -p never", 19 | "build-setapp-arm64": "electron-builder --config ./build/electron.arm64.config.js --mac --arm64 -p never", 20 | "build-setapp-x64": "electron-builder --config ./build/electron.x64.config.js --mac --x64 -p never", 21 | "build-universal": "node ./build/build-universal.js", 22 | "setapp-notarize": "node ./build/setapp-notarize.js", 23 | "rebuild-arm64": "./node_modules/.bin/electron-rebuild --arch=arm64", 24 | "rebuild-x64": "./node_modules/.bin/electron-rebuild --arch=x64" 25 | }, 26 | "dependencies": { 27 | "@electron/remote": "^1.0.4", 28 | "auto-launch": "^5.0.5", 29 | "body-parser": "^1.19.0", 30 | "cross-env": "^7.0.3", 31 | "electron-store": "^8.0.1", 32 | "electron-updater": "4.3.9", 33 | "macos-version": "^4.0.0", 34 | "node-mac-permissions": "^2.2.0", 35 | "one-liner": "^1.0.3", 36 | "osa-imessage": "^2.4.2", 37 | "osa2": "^0.0.0", 38 | "sqlite": "^2.8.0", 39 | "stopwords": "^0.0.9" 40 | }, 41 | "devDependencies": { 42 | "ava": "^0.25.0", 43 | "electron": "11.2.1", 44 | "electron-builder": "^22.11.9", 45 | "electron-notarize": "^1.0.0", 46 | "electron-rebuild": "^2.3.5" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/preload.js: -------------------------------------------------------------------------------- 1 | const { 2 | remote, 3 | ipcRenderer 4 | } = require("electron"); 5 | 6 | const config = require("./config.js"); 7 | 8 | window.ipcRenderer = ipcRenderer; 9 | window.ohtipiConfig = config; 10 | window.ohtipiApi = { 11 | "send": function (data) { 12 | ipcRenderer.send("request", data); 13 | } 14 | }; -------------------------------------------------------------------------------- /src/setapp.js: -------------------------------------------------------------------------------- 1 | const { 2 | app, 3 | BrowserWindow 4 | } = require('electron'); 5 | 6 | const config = require("./config.js"); 7 | 8 | const Setapp = new class Setapp { 9 | constructor() { 10 | this.isActive = config.build.setApp 11 | // eslint-disable-next-line 12 | console.log('is setapp:', this.isActive) 13 | } 14 | init() { 15 | if (!this.isActive) { 16 | return 17 | } 18 | 19 | this.setapp = require('./binaries/setapp-nodejs-wrapper.node') 20 | 21 | } 22 | reportUsageEvent(name = null) { 23 | if (!this.isActive || !this.setapp || !name) { 24 | return 25 | } 26 | this.setapp.SCReportUsageEvent(name) 27 | } 28 | }() 29 | 30 | module.exports = { 31 | Setapp: Setapp 32 | } -------------------------------------------------------------------------------- /webextension-example/background.js: -------------------------------------------------------------------------------- 1 | var ws; 2 | var connected = false; 3 | var badgeTimeout; 4 | 5 | function openSocket() { 6 | ws = new WebSocket("ws://localhost:9097/"); 7 | 8 | ws.onopen = function () { 9 | connected = true; 10 | chrome.browserAction.setBadgeText({ 11 | "text": "✓" 12 | }); 13 | 14 | if (badgeTimeout) clearTimeout(badgeTimeout); 15 | 16 | badgeTimeout = setTimeout(() => { 17 | chrome.browserAction.setBadgeText({ 18 | "text": "" 19 | }); 20 | }, 2000); 21 | }; 22 | 23 | ws.onmessage = function (e) { 24 | if (e.data) { 25 | const data = JSON.parse(e.data); 26 | const otpCode = data.otpRaw; 27 | 28 | chrome.tabs.getAllInWindow(null, function (tabs) { 29 | tabs.forEach(tab => { 30 | if (!tab.active) return; 31 | chrome.tabs.sendMessage(tab.id, { 32 | action: "otp-display", 33 | otpCode 34 | }); 35 | }) 36 | }); 37 | 38 | chrome.browserAction.setBadgeText({ 39 | "text": "💬" 40 | }); 41 | 42 | if (badgeTimeout) clearTimeout(badgeTimeout); 43 | 44 | badgeTimeout = setTimeout(() => { 45 | chrome.browserAction.setBadgeText({ 46 | "text": "" 47 | }); 48 | }, 5000); 49 | 50 | } 51 | }; 52 | 53 | ws.onclose = function (e) { 54 | connected = false; 55 | ws = undefined; 56 | 57 | chrome.browserAction.setBadgeText({ 58 | "text": "!" 59 | }); 60 | }; 61 | 62 | ws.onerror = function (e) { 63 | chrome.browserAction.setBadgeText({ 64 | "text": "!" 65 | }); 66 | setTimeout(() => { 67 | openSocket(); 68 | }, 3000) 69 | } 70 | } 71 | 72 | (function () { 73 | openSocket(); 74 | 75 | chrome.browserAction.onClicked.addListener(function () { 76 | if (ws === undefined) { 77 | openSocket(); 78 | } else { 79 | // if (ws.readyState === WebSocket.OPEN) { 80 | // ws.send(prompt('send text')); 81 | // } 82 | } 83 | }); 84 | })(); -------------------------------------------------------------------------------- /webextension-example/content.js: -------------------------------------------------------------------------------- 1 | // inline content script styles 2 | const defaultStyle = ` 3 | position: absolute; 4 | z-index: 999999 !important; 5 | background: rgba(0,0,0,0.9); 6 | color: white; 7 | padding: 14px; 8 | user-select: all !important; 9 | cursor: pointer; 10 | border-radius: 7px; 11 | border: 1px solid rgba(255,255,255,0.2); 12 | margin-top: 140px; 13 | font-size: 14px; 14 | display: flex; 15 | flex-direction: row; 16 | text-align: left; 17 | justify-content: start; 18 | ` 19 | 20 | const hoverStyle = ` 21 | position: absolute; 22 | z-index: 999999 !important; 23 | background: #0361ff; 24 | color: white; 25 | padding: 14px; 26 | user-select: all !important; 27 | cursor: pointer; 28 | border-radius: 7px; 29 | border: 1px solid rgba(255,255,255,0.2); 30 | margin-top: 140px; 31 | font-size: 14px; 32 | display: flex; 33 | flex-direction: row; 34 | text-align: left; 35 | justify-content: start; 36 | ` 37 | 38 | const svgIcon = ` 39 | 40 | ` 41 | 42 | chrome.runtime.onMessage.addListener((message, callback) => { 43 | if (message.action == "otp-display") { 44 | const otpCode = message.otpCode; 45 | const inputElements = document.getElementsByTagName("input"); 46 | var div = document.createElement('div'); 47 | 48 | const selectAndPastePassword = () => { 49 | window.getSelection().selectAllChildren( 50 | document.getElementById("otp-passcode-text") 51 | ); 52 | document.execCommand("copy"); 53 | inputElements[0].focus(); 54 | document.execCommand("paste"); 55 | div.style = "display: none"; 56 | }; 57 | 58 | const init = () => { 59 | div.id = "otp-passcode"; 60 | div.innerHTML = ` 61 | 62 | ${svgIcon} 63 | 64 |
65 |
Fill code ${otpCode}
66 |
From Messages
67 |
68 | `; 69 | div.style = defaultStyle; 70 | 71 | div.onmouseenter = () => { 72 | div.style = hoverStyle; 73 | } 74 | 75 | div.onmouseleave = () => { 76 | div.style = defaultStyle; 77 | } 78 | 79 | div.onclick = () => { 80 | selectAndPastePassword(); 81 | } 82 | } 83 | 84 | init(); 85 | 86 | // attach to first page element in page 87 | inputElements[0].parentElement.appendChild(div); 88 | } 89 | }); -------------------------------------------------------------------------------- /webextension-example/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/webextension-example/icon128.png -------------------------------------------------------------------------------- /webextension-example/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/webextension-example/icon16.png -------------------------------------------------------------------------------- /webextension-example/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YacTeam/ohtipi/b2ef7a1ef0674306e44286af79cc0540efccbc2d/webextension-example/icon48.png -------------------------------------------------------------------------------- /webextension-example/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ohtipi", 3 | "version": "1.0", 4 | "permissions": [ 5 | "tabs", 6 | "clipboardWrite", 7 | "clipboardRead" 8 | ], 9 | "icons": { 10 | "16": "icon16.png", 11 | "48": "icon48.png", 12 | "128": "icon128.png" 13 | }, 14 | "description": "iMessage OTP", 15 | "background": { 16 | "persistent": true, 17 | "scripts": [ 18 | "background.js" 19 | ] 20 | }, 21 | "content_scripts": [{ 22 | "matches": [""], 23 | "js": ["content.js"] 24 | }], 25 | "browser_action": { 26 | "title": "Ohtipi" 27 | }, 28 | "manifest_version": 2 29 | } -------------------------------------------------------------------------------- /webextension-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ohtipi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | --------------------------------------------------------------------------------