npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

web-launch-kit

v0.0.2

Published

TypeScript library for launching external apps and communication intents from the web — deep links / custom schemes, tel, sms, mailto, maps, a file picker, and system-settings deep links, with graceful store/web fallbacks.

Readme

npm bundle size types

web-launch-kit

A TypeScript library for launching external apps and communication intents from the web. Handles deep links / custom schemes, Android intent:// URLs, iOS universal links, and App Store / web-store fallbacks — trying each candidate in order until one succeeds. Also covers tel, sms, mailto, a file picker, and system-settings deep links.

npm install web-launch-kit

The bundle is self-contained (OS/locale detection is inlined) — no peer scripts required.

API at a glance

LaunchKit is a singleton.

| Member | Signature | Description | | --- | --- | --- | | LaunchKit.version | string | The installed package version | | LaunchKit.SettingType | enum | Setting panes: General, Network, Display, Appearance, Accessibility, Battery, Datetime, Language, Accounts, Storage | | LaunchKit.app(options?) | Promise<AppOpenedBy> | Launch an app via the best available route; resolves with which route opened it | | LaunchKit.telephone(options?) | Promise<void> | Open the dialer (tel:) | | LaunchKit.message(options?) | Promise<void> | Open the SMS composer (sms:) | | LaunchKit.mail(options?) | Promise<void> | Open the mail composer (mailto:) | | LaunchKit.map(options?) | Promise<void> | Open a map by query, coordinate, or directions; native app per-OS with a Google Maps web fallback | | LaunchKit.filepicker(options?) | Promise<File[]> | Pick files or a directory (File System Access API, with input fallback) | | LaunchKit.setting(type?) | Promise<void> | Open a system-settings pane where supported | | LaunchKit.utils | object | canOpenIntent / canOpenUniversal / canOpenSetting getters, plus async getTrackId / getProductId |

AppOpenedBy is one of: "scheme", "universal", "intent", "fallback", "store".


Launching an app

app() takes per-platform options and only acts on the block matching the current OS. It builds an ordered list of candidates and tries each until one launches the app, resolving with the route that worked.

flowchart TD
    A([LaunchKit.app called]) --> B{Detect OS via PlatformKit}
    B -->|unknown| E1([Reject: unsupported OS])
    B -->|android| AND
    B -->|ios| IOS
    B -->|windows| WIN
    B -->|macos| MAC

    subgraph AND["android · resolveOptions"]
        A1{"intent given, but scheme /<br/>packageName / fallback missing?"}
        A1 -->|yes| A2["parseIntentURL:<br/>derive scheme · packageName · fallback"]
        A1 -->|no| A3
        A2 --> A3{"scheme given, but intent missing?"}
        A3 -->|yes| A4["createIntentURL:<br/>scheme + packageName + fallback"]
        A3 -->|no| A5
        A4 --> A5["Priority list:<br/>intent (if canOpenIntent) ⭢ scheme (if canOpenScheme)<br/>⭢ fallback ⭢ app store (if canOpenScheme) ⭢ web store"]
    end

    subgraph IOS["ios · resolveOptions"]
        I1{"bundleId given, but trackId missing?"}
        I1 -->|yes| I2["getTrackId:<br/>iTunes lookup API (bundleId ⭢ trackId)"]
        I1 -->|no| I3
        I2 --> I3["Priority list:<br/>universal (if canOpenUniversal) ⭢ scheme (if canOpenScheme)<br/>⭢ fallback ⭢ app store (if canOpenScheme) ⭢ web store"]
    end

    subgraph WIN["windows · resolveOptions"]
        W1{"packageFamilyName given,<br/>but productId missing?"}
        W1 -->|yes| W2["getProductId:<br/>packageFamilyName ⭢ productId"]
        W1 -->|no| W3
        W2 --> W3["Priority list:<br/>scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by productId)"]
    end

    subgraph MAC["macos · resolveOptions"]
        M1{"bundleId given, but trackId missing?"}
        M1 -->|yes| M2["getTrackId:<br/>iTunes lookup API (bundleId ⭢ trackId)"]
        M1 -->|no| M3
        M2 --> M3["Priority list:<br/>scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by trackId)"]
    end

    AND --> P
    IOS --> P
    WIN --> P
    MAC --> P

    P{"Any URL candidates?"} -->|no| E2([Reject: no openable URL candidates])
    P -->|yes| Q{"Next candidate type?"}
    Q -->|"function fallback"| R["Invoke fallback function"] --> G
    Q -->|"URL string"| O["openURL(index, url, timeout)"]
    O --> F{Opened?}
    F -->|yes| G(["Resolve AppOpenedBy:<br/>'intent' · 'universal' · 'scheme' · 'fallback' · 'store'"])
    F -->|no| H{"Candidates remaining?"}
    H -->|yes| Q
    H -->|no| E3(["Reject: all attempted URLs failed<br/>(error lists every tried URL)"])

    subgraph openURL["openURL · app-switch detection"]
        T0["Register blur + visibilitychange listeners"] --> T1{"Document focused?"}
        T1 -->|no| T2["restoreFocus:<br/>window ⭢ body ⭢ hidden input"]
        T1 -->|yes| T3
        T2 --> T3{"Environment?"}
        T3 -->|cordova| T4["InAppBrowser.open / window.open ('_system')"]
        T3 -->|browser| T5{"userActivation active<br/>or first attempt?"}
        T5 -->|yes| T6["top.location.href = url"]
        T5 -->|no| T7["Hidden anchor + synthetic click"]
        T6 --> T8["+ hidden iframe (removed after 500ms)"]
        T7 --> T8
        T4 --> T9
        T8 --> T9{"blur / hidden fired?"}
        T9 -->|"yes ⭢ wait focus"| T10([resolve: app opened])
        T9 -->|"no, until timeout"| T11([reject: app not detected])
    end

    O -.->|delegates to| T0
import LaunchKit from 'web-launch-kit'

const openedBy = await LaunchKit.app({
  android: {
    scheme: 'myapp://profile/42',
    packageName: 'com.example.myapp',
    allowAppStore: true,
    allowWebStore: true,
  },
  ios: {
    universal: 'https://example.com/profile/42',
    scheme: 'myapp://profile/42',
    bundleId: 'com.example.myapp', // resolved to a trackId for the store fallback
    allowAppStore: true,
  },
})

console.log(openedBy) // "universal" | "scheme" | "intent" | "fallback" | "store"

Per-platform fields: Android accepts intent / scheme / packageName / fallback (scheme ⇄ intent are derived from each other); iOS accepts universal / scheme / bundleId / trackId; Windows accepts scheme / packageFamilyName / productId; macOS accepts scheme / bundleId / trackId. All accept fallback, timeout, allowAppStore, allowWebStore.

Android and iOS additionally accept assumeAllowedInApp — declare it true only when your app is whitelisted by a partner-gated in-app browser (e.g. Weibo) so scheme / universal-link candidates are kept there; it never bypasses hard-blocked webviews or OS version requirements.

Communication intents

import LaunchKit from 'web-launch-kit'

await LaunchKit.telephone({ to: '+821012345678' })

await LaunchKit.message({ to: '+821012345678', body: 'hello' })

await LaunchKit.mail({
  to: ['[email protected]', '[email protected]'],
  cc: '[email protected]',
  subject: 'Hi',
  body: 'from web-launch-kit',
})

File picker

import LaunchKit from 'web-launch-kit'

// Files (uses showOpenFilePicker where available, falls back to <input type=file>)
const files = await LaunchKit.filepicker({ accept: ['image/*', '.pdf'], multiple: true })

// A directory (recursive; webkitRelativePath is populated)
const tree = await LaunchKit.filepicker({ directory: true })

Maps

map() takes an intent — a search query, a coordinate, or a route — and builds the right URL for the current OS (maps:// on iOS/macOS, geo: on Android, bingmaps: on Windows), falling back to Google Maps on the web.

import LaunchKit from 'web-launch-kit'

// Search for a place
await LaunchKit.map({ query: 'Seoul City Hall' })

// Show a coordinate with a labelled pin
await LaunchKit.map({ coordinate: [37.5665, 126.9780], label: 'Seoul City Hall', zoom: 15 })

// Directions (origin defaults to current location when omitted)
await LaunchKit.map({ directions: { destination: 'Seoul Station', origin: [37.5665, 126.9780] } })

Provide exactly one of query / coordinate / directions; when several are set, directions wins, then coordinate, then query. A label with coordinate renders a named pin on iOS/macOS/Windows and is ignored on Android, where geo: handlers other than Google Maps misread the labelled form as a search. Android's geo: scheme also has no standard directions support, so routes use the Google Maps fallback there, and on Windows the discontinued Maps app means bingmaps: usually defers to the web fallback too.

System settings

import LaunchKit from 'web-launch-kit'

if (LaunchKit.utils.canOpenSetting) {
  await LaunchKit.setting(LaunchKit.SettingType.Network)
}

CommonJS / UMD

The bundle is built with exports: "named", so the singleton lives under .default:

const { default: LaunchKit } = require('web-launch-kit')
<script src="https://unpkg.com/web-launch-kit/dist/launch-kit.umd.min.js"></script>
<script>
    window.LaunchKit.default.telephone({ to: '+821012345678' })
</script>

Notes

  • Deep links need a real user gesture. Universal links and custom schemes are unreliable when triggered programmatically or from a same-origin context — they fall back to the web instead of opening the app. Call app() from a click/tap handler.
  • app() resolves with the route, not a guarantee of launch. Detection relies on focus/visibility heuristics with per-OS timeouts; a resolved AppOpenedBy means that candidate was attempted and the page appeared to background, not a hard confirmation.
  • utils.getTrackId / getProductId are async; the named getTrackId / getProductId exports are synchronous (blocking XHR) and intended for internal/legacy use.
  • Store-id lookups depend on remote APIs (iTunes Lookup, Microsoft display catalog) and are cached for one hour; failures resolve to undefined rather than throwing.
  • In-app browsers are gated per candidate. Inside webviews that block launches (WeChat, QQ, Qzone, Baidu; Weibo unless partnered), dead candidates — custom schemes, intent://, and store schemes like market:// — are skipped up front instead of burning their timeouts, so the chain falls straight through to fallback / web store (the only routes that work there). Whitelisted partner apps opt back in with assumeAllowedInApp: true.
  • map() uses the per-OS timeout for each candidate.