{"_id":"@aun-abbas/expo-pin-shortcut","_rev":"2-3db42af269e52b30791fd0ac844b0da2","name":"@aun-abbas/expo-pin-shortcut","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@aun-abbas/expo-pin-shortcut","version":"0.1.0","keywords":["expo","react-native","android","shortcut","pin-shortcut","home-screen","home-screen-shortcut","launcher","expo-module"],"author":{"name":"Aun Abbas","email":"imaunabbas@gmail.com"},"license":"MIT","_id":"@aun-abbas/expo-pin-shortcut@0.1.0","maintainers":[{"name":"imaunabbas","email":"imaunabbas@gmail.com"}],"homepage":"https://github.com/imAunAbbas/expo-pin-shortcut#readme","bugs":{"url":"https://github.com/imAunAbbas/expo-pin-shortcut/issues"},"dist":{"shasum":"ba66cf32eb875b52af3bdcc639b22d1740e703a2","tarball":"https://registry.npmjs.org/@aun-abbas/expo-pin-shortcut/-/expo-pin-shortcut-0.1.0.tgz","fileCount":10,"integrity":"sha512-tObz36hbfBH9P/k8lfXbiR2hE5hs/q/FvLhM9F73/KrPtelvDq/202F2X9/oX3nYHeT9N3PhSm8kFz5hN+Vzlw==","signatures":[{"sig":"MEYCIQD15lgAMeppCzxcvvbnNiqTkYdJi2aHDLHPhW0VyOZlmAIhANVEdKaUtiA5GF06KQcx04O7WTPxxX1dDCl6qChqdcSH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":17154},"main":"index.ts","types":"index.ts","gitHead":"cad6c9325378d880ed14c1a71f21d660532405fb","_npmUser":{"name":"imaunabbas","email":"imaunabbas@gmail.com"},"repository":{"url":"git+https://github.com/imAunAbbas/expo-pin-shortcut.git","type":"git"},"_npmVersion":"10.9.4","description":"Pin a shortcut to the Android home screen from your Expo or React Native app. Wraps ShortcutManagerCompat.requestPinShortcut. Android-only by design (iOS does not expose a programmatic equivalent).","directories":{},"_nodeVersion":"22.21.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"expo":"*","react":"*","react-native":"*","expo-modules-core":"*"},"_npmOperationalInternal":{"tmp":"tmp/expo-pin-shortcut_0.1.0_1777372964687_0.009608297160822898","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aun-abbas/expo-pin-shortcut","version":"0.2.0","description":"Pin a shortcut to the Android home screen from your Expo or React Native app. Wraps ShortcutManagerCompat.requestPinShortcut. Android-only by design (iOS does not expose a programmatic equivalent).","main":"index.ts","types":"index.ts","keywords":["expo","react-native","android","shortcut","pin-shortcut","home-screen","home-screen-shortcut","launcher","expo-module"],"repository":{"type":"git","url":"git+https://github.com/imAunAbbas/expo-pin-shortcut.git"},"bugs":{"url":"https://github.com/imAunAbbas/expo-pin-shortcut/issues"},"homepage":"https://github.com/imAunAbbas/expo-pin-shortcut#readme","author":{"name":"Aun Abbas","email":"imaunabbas@gmail.com"},"license":"MIT","publishConfig":{"access":"public"},"peerDependencies":{"expo":"*","expo-modules-core":"*","react":"*","react-native":"*"},"_id":"@aun-abbas/expo-pin-shortcut@0.2.0","gitHead":"86a6e753878ef84e1bd84d8a5491e7ab581b3379","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-AhZOomKFEDEhIIV0r1rYOw6fQ9cmmiUVTNl0w0JnL2a4mfD7HkkTeA0yHPHJhgiLrCnv6s1jZDdNE4XDuqwV8A==","shasum":"70a5f74ac1ae0d84df7cc502d1ed4fceaa440481","tarball":"https://registry.npmjs.org/@aun-abbas/expo-pin-shortcut/-/expo-pin-shortcut-0.2.0.tgz","fileCount":11,"unpackedSize":19573,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCRVode706zgQEYUVcVuLmUOBGjkMgWr3ULbrEuVwFMEwIhAI0Vm7Ul1sKm23CKHO8aaVa5hufH+KOEMGQ+BC2B1FT3"}]},"_npmUser":{"name":"imaunabbas","email":"imaunabbas@gmail.com"},"directories":{},"maintainers":[{"name":"imaunabbas","email":"imaunabbas@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/expo-pin-shortcut_0.2.0_1777874588966_0.957507467789634"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-28T10:42:44.608Z","modified":"2026-05-04T06:03:09.249Z","0.1.0":"2026-04-28T10:42:44.843Z","0.2.0":"2026-05-04T06:03:09.113Z"},"bugs":{"url":"https://github.com/imAunAbbas/expo-pin-shortcut/issues"},"author":{"name":"Aun Abbas","email":"imaunabbas@gmail.com"},"license":"MIT","homepage":"https://github.com/imAunAbbas/expo-pin-shortcut#readme","keywords":["expo","react-native","android","shortcut","pin-shortcut","home-screen","home-screen-shortcut","launcher","expo-module"],"repository":{"type":"git","url":"git+https://github.com/imAunAbbas/expo-pin-shortcut.git"},"description":"Pin a shortcut to the Android home screen from your Expo or React Native app. Wraps ShortcutManagerCompat.requestPinShortcut. Android-only by design (iOS does not expose a programmatic equivalent).","maintainers":[{"name":"imaunabbas","email":"imaunabbas@gmail.com"}],"readme":"# @aun-abbas/expo-pin-shortcut\n\n[![npm](https://img.shields.io/npm/v/@aun-abbas/expo-pin-shortcut.svg)](https://www.npmjs.com/package/@aun-abbas/expo-pin-shortcut)\n[![Android](https://img.shields.io/badge/Android-✓-green)](#)\n[![iOS](https://img.shields.io/badge/iOS-no--op-lightgrey)](#ios)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nPin a shortcut to the Android home screen from your Expo or React Native app — like Chrome's \"Add to Home Screen\" for websites, but invokable from your own app for arbitrary deep links.\n\n> **Android-only by design.** iOS does not provide a programmatic API for adding home screen icons. On iOS this module's `pin()` throws `E_UNSUPPORTED` and `isSupported()` returns `false`. See [iOS](#ios) below for the rationale.\n\n## Pinned shortcut vs. App shortcut\n\nThis package creates **pinned shortcuts** — actual icons placed on the user's home screen, separately from your app icon, with their own labels and (optionally) custom favicons. Tapping one fires a deep link into your app.\n\nIt does **not** create *app shortcuts* (the menu that pops up when long-pressing your app icon). For that, use [`expo-quick-actions`](https://github.com/EvanBacon/expo-quick-actions) or [`@rn-bridge/react-native-shortcuts`](https://www.npmjs.com/package/@rn-bridge/react-native-shortcuts) — both of which are about the long-press menu, not the home screen itself.\n\n## Installation\n\n### Expo\n\n```sh\nyarn add @aun-abbas/expo-pin-shortcut\nnpx expo prebuild --clean\nyarn android\n```\n\nThe module autolinks via Expo's module system. No manual configuration needed.\n\n### Bare React Native (without Expo)\n\n```sh\nyarn add @aun-abbas/expo-pin-shortcut\nnpx install-expo-modules    # only the first time you add an Expo module\ncd android && ./gradlew assembleDebug\n```\n\n`npx install-expo-modules` installs `expo` + `expo-modules-core` and wires up the autolinking. Once installed, all Expo modules (including this one) work in a bare RN project.\n\n## Usage\n\n```ts\nimport { appShortcut } from '@aun-abbas/expo-pin-shortcut';\n\nif (appShortcut.isSupported()) {\n  try {\n    const ok = await appShortcut.pin({\n      id: 'unique-shortcut-id',\n      label: 'My Shortcut',\n      uri: 'myapp://route?param=value',\n      iconUrl: 'https://example.com/icon.png',\n    });\n    if (ok) {\n      // Pin request was accepted by the launcher; the user has been prompted.\n    }\n  } catch (err) {\n    // err.code: 'E_UNSUPPORTED' | 'E_INVALID_ARGS' | 'E_INVALID_URI' | 'E_PIN_FAILED'\n    console.warn(err);\n  }\n}\n```\n\nFor tapping the shortcut to actually open your app, your app must register an intent filter for the URI scheme you used. With Expo Router, this is automatic if `uri` uses your app's `scheme` from `app.json`.\n\n## API\n\n### `appShortcut.isSupported(): boolean`\n\nSynchronous. Returns `true` if the device's default launcher accepts pinned shortcut requests.\n\n- **Android:** wraps `ShortcutManagerCompat.isRequestPinShortcutSupported()`. Most modern launchers (Pixel Launcher, Nova, Samsung One UI) return `true`. Some AOSP emulator launchers return `false`.\n- **iOS:** always returns `false`.\n- **Web:** always returns `false`.\n\n### `appShortcut.pin(input: PinShortcutInput): Promise<boolean>`\n\nAsks the launcher to pin a shortcut. The launcher typically shows a system dialog asking the user to confirm.\n\n```ts\ninterface PinShortcutInput {\n  /** Stable identifier for the shortcut. Re-using an existing id replaces that pin. */\n  id: string;\n  /** Label shown under the icon on the home screen. */\n  label: string;\n  /** Deep link URI opened when the shortcut is tapped, e.g. `myapp://route`. */\n  uri: string;\n  /** Optional remote favicon URL. Falls back to your app's launcher icon. */\n  iconUrl?: string | null;\n}\n```\n\n**Resolves with `true`** if the launcher accepted the pin request (the user has been shown the confirm dialog). Note: the resolved `true` does **not** guarantee the user actually accepted — Android does not surface that signal back to your app reliably. It only confirms the request was delivered.\n\n**Resolves with `false`** on iOS / web (no-op).\n\n**Throws `CodedException`** on Android with one of:\n- `E_UNSUPPORTED` — launcher does not support pinning. Falls back gracefully; show a toast.\n- `E_INVALID_ARGS` — `id`, `label`, or `uri` was empty.\n- `E_INVALID_URI` — `uri` could not be parsed.\n- `E_PIN_FAILED` — anything else went wrong (icon download crash, system error). The exception message includes the underlying class + message.\n\n## Surviving launcher-icon swaps\n\nApps that swap their launcher icon at runtime (e.g. via `expo-alternate-app-icons`) typically do so by enabling an `<activity-alias>` and disabling the original `MainActivity`. By default, Android removes pinned shortcuts whose owning activity has been disabled, so every shortcut your users pinned would vanish on the next icon switch.\n\nSince `0.2.0`, this module bundles a tiny always-enabled `ShortcutHostActivity` and anchors every pinned shortcut to it via `ShortcutInfo.setActivity(...)`. The host is never launched (its `onCreate` calls `finish()` immediately) — it exists only as stable metadata. Pinned shortcuts now survive arbitrary icon swaps.\n\n> Existing shortcuts pinned with `0.1.0` were anchored to `MainActivity` and cannot be retroactively re-anchored — Android pins are immutable in that respect. Users who already had pinned shortcuts will need to re-pin them once after upgrading.\n\nFor the click intent itself to survive the swap, your launcher activity's deep-link `<intent-filter>` must also be present on each `<activity-alias>` (otherwise `ACTION_VIEW` can't resolve while an alternate icon is active). The host activity solves the *ownership* half of the problem; the intent-filter mirroring is your app's responsibility.\n\n## Icon handling\n\nIf you pass `iconUrl`, the native side downloads the bitmap on a background thread, then renders it onto a 432×432 white square with ~20% padding to survive launcher icon masking (adaptive icon shape). If the download fails or `iconUrl` is null, the launcher icon (`R.mipmap.ic_launcher`) is used instead.\n\n## iOS\n\niOS does not expose a programmatic API for adding home screen icons. This is an explicit Apple platform decision, not an oversight, and has not changed through iOS 26.\n\nThe closest analogues — `UIApplicationShortcutItem` (long-press app menu), Shortcuts.app workflows, Universal Links + Safari \"Add to Home Screen\" — are all the wrong shape for this feature. Rather than ship a half-implementation that confuses users, this module is explicitly Android-only on iOS.\n\nIf your use case can be served by long-press app shortcuts, see [`expo-quick-actions`](https://github.com/EvanBacon/expo-quick-actions).\n\n## Why this exists\n\nI wanted Chrome's \"Add to Home Screen\" experience for arbitrary deep links inside my own app. None of the existing `react-native-*-shortcuts` packages on npm wrap `requestPinShortcut` — they all do app shortcuts (the long-press menu). So I built this.\n\n## License\n\n[MIT](LICENSE) © Aun Abbas\n","readmeFilename":"README.md"}