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

electron-launch-handler

v0.1.0

Published

Single instance enforcement and deep link handling for Electron apps

Downloads

1,256

Readme

electron-launch-handler

CI npm version npm downloads

Single-instance and deep-link plumbing for Electron apps.

Electron gives you deep links through different paths depending on the platform: open-url on macOS, command-line arguments on Windows and Linux, and second-instance when the app is already running. This package normalizes that startup path so your app code can handle URLs and relaunches from one place.

What It Handles

  • Single-instance lock acquisition
  • Custom protocol registration
  • Launch-time and already-running deep links
  • Plain relaunches without a deep link
  • Readiness queues for startup, auth, onboarding, or workspace loading
  • Squirrel.Windows installer events

Installation

npm install electron-launch-handler
# or
pnpm add electron-launch-handler

Quick Start

import { setupInstance } from 'electron-launch-handler'
import { app, BrowserWindow } from 'electron'

let mainWindow: BrowserWindow | null = null

const instance = setupInstance({
  protocols: ['myapp'],
  onDeepLink: (url, context) => {
    if (!mainWindow) {
      mainWindow = new BrowserWindow({ width: 1200, height: 800 })
    }

    console.log('Deep link:', url)
    console.log('Intent:', context.intent)
  },
})

if (instance.shouldQuit) {
  app.quit()
} else {
  app.whenReady().then(() => {
    instance.processPendingDeepLinks()
  })
}

How It Works

  1. The library acquires a single-instance lock.
  2. Deep links are collected until you call processPendingDeepLinks().
  3. Once processed, future deep links are delivered directly to onDeepLink.

When another instance launches with a deep link, the running app receives it via onDeepLink. Relaunches without a deep link go to onSecondInstance instead.

Lock Failure Handling

If another instance is already running, the new process will have instance.shouldQuit === true. You can also hook onInstanceLockFailed to record telemetry or perform cleanup before exiting.

const instance = setupInstance({
  protocols: ['myapp'],
  onInstanceLockFailed: () => {
    console.log('Another instance is already running')
  },
})

if (instance.shouldQuit) {
  app.quit()
}

Deep Link Intent

DeepLinkContext.intent tells you why the deep link was delivered:

  • launch: the app was launched by a deep link
  • open-url: the app was already running when the deep link was received
onDeepLink: (url, context) => {
  if (context.intent === 'launch') {
    // App launched by this URL
  } else {
    // URL received while app was running
  }
}

Readiness

Deep links are queued until your app opts in to handling them:

app.whenReady().then(() => {
  instance.processPendingDeepLinks()
})

Use queueDeepLink(url) for URLs your own code receives before that point. After readiness, return { action: 'defer' } from onDeepLink when the URL is valid but another app condition is not ready yet. Deferred links are held until processDeferredDeepLinks().

Common Patterns

Onboarding Flows

let isOnboardingComplete = false

const instance = setupInstance({
  protocols: ['myapp'],
  onDeepLink: (url) => {
    if (!isOnboardingComplete) {
      return { action: 'defer' }
    }

    handleDeepLink(url)
  },
})

app.whenReady().then(() => {
  instance.processPendingDeepLinks()
})

const completeOnboarding = () => {
  isOnboardingComplete = true
  instance.processDeferredDeepLinks()
}

Plain Relaunches

setupInstance({
  protocols: ['myapp'],
  onDeepLink: handleDeepLink,
  onSecondInstance: ({ deepLinkUrl }) => {
    if (!deepLinkUrl) {
      focusMainWindow()
    }
  },
})

Development Protocols

const isDev = !app.isPackaged

const instance = setupInstance({
  protocols: isDev ? ['myapp-dev'] : ['myapp'],
  onDeepLink: (url) => {
    console.log('Deep link received:', url)
  },
})

Logging

Provide a logger to capture lifecycle events such as lock acquisition, protocol registration, queueing, dispatch, and errors.

import log from 'electron-log'

const instance = setupInstance({
  protocols: ['myapp'],
  onDeepLink: (url) => {
    // ...
  },
  logger: {
    debug: (msg) => log.debug(msg),
    info: (msg) => log.info(msg),
    error: (msg) => log.error(msg),
  },
})

Platform-Specific Options

const instance = setupInstance({
  protocols: ['myapp'],
  onDeepLink: (url) => {
    // ...
  },

  // Windows: Handle Squirrel.Windows installer events
  windows: {
    handleSquirrelEvents: true,
    squirrelOptions: {
      createDesktopShortcut: true,
      createStartMenuShortcut: true,
      shortcutName: 'My App',
    },
  },

  // Linux: Specify desktop file name
  linux: {
    desktopFileName: 'my-app',
  },
})

Squirrel.Windows Notes

When windows.handleSquirrelEvents is enabled, the library handles --squirrel-* events for install/update/uninstall:

  • --squirrel-install: Creates shortcuts
  • --squirrel-updated: Updates shortcuts
  • --squirrel-uninstall: Removes shortcuts
  • --squirrel-obsolete: Exits cleanly during version replacement

Use windows.squirrelOptions to control shortcut behavior.

API Reference

setupInstance(options)

Main entry point. Returns an InstanceManager object.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | protocols | string[] | [] | Protocol schemes to register | | onDeepLink | DeepLinkHandler | - | Called when a deep link is received. Can return a DeepLinkDeferral. | | onSecondInstance | SecondInstanceHandler | - | Called when another instance launches | | onInstanceLockFailed | () => void | - | Called when lock acquisition fails | | logger | Logger | no-op | Logger instance | | windows | WindowsOptions | - | Windows-specific options | | linux | LinuxOptions | - | Linux-specific options | | macos | MacOSOptions | - | macOS-specific options |

Returns: InstanceManager

| Property/Method | Type | Description | |-----------------|------|-------------| | shouldQuit | boolean | Whether this instance should quit | | processPendingDeepLinks() | () => void | Process queued deep links and mark handler as ready | | getPendingDeepLinks() | () => string[] | Get pending deep links without processing | | clearPendingDeepLinks() | () => void | Clear pending deep links without processing | | queueDeepLink(url) | (url: string, intent?: DeepLinkIntent) => void | Queue a deep link for later processing | | deferDeepLink(url) | (url: string, intent?: DeepLinkIntent) => void | Hold a deep link until processDeferredDeepLinks() | | processDeferredDeepLinks() | () => void | Process deep links held by deferDeepLink() | | getDeferredDeepLinks() | () => string[] | Get deferred deep links without processing | | clearDeferredDeepLinks() | () => void | Clear deferred deep links without processing | | unregisterProtocols() | () => void | Unregister protocol handlers (typically only needed for testing) | | dispose() | () => void | Remove installed listeners and unregister protocols |

DeepLinkContext

| Property | Type | Description | |----------|------|-------------| | url | string | Original URL string | | parsed | URL | Parsed URL object | | protocol | string | Protocol without :// | | host | string | URL host | | path | string | URL path | | params | URLSearchParams | Query parameters | | hash | string | URL hash/fragment | | intent | 'launch' \| 'open-url' | Why the deep link was delivered |

DeepLinkDeferral

Return { action: 'defer' } from onDeepLink to redeliver the same URL on the next processDeferredDeepLinks() call.

SecondInstanceContext

| Property | Type | Description | |----------|------|-------------| | argv | string[] | Command-line arguments from the second instance | | workingDirectory | string | Working directory from the second instance | | deepLinkUrl | string \| undefined | Deep link found in argv, if present |

parseDeepLink(url)

Parse a deep link URL manually.

import { parseDeepLink } from 'electron-launch-handler'

const result = parseDeepLink('myapp://open/document?id=123')
// {
//   url: 'myapp://open/document?id=123',
//   parsed: URL { ... },
//   protocol: 'myapp',
//   host: 'open',
//   path: '/document',
//   params: URLSearchParams { 'id' => '123' },
//   hash: ''
// }

Platform Notes

macOS

  • Deep links arrive via the open-url app event
  • Protocol registration uses app.setAsDefaultProtocolClient()
  • Works with both Intel and Apple Silicon

Windows

  • Deep links arrive via command-line arguments
  • Protocol registration uses app.setAsDefaultProtocolClient()
  • Squirrel.Windows Support: Installer events are handled automatically
  • This library handles URL protocols, not OS-level file associations

Linux

  • Deep links arrive via command-line arguments
  • Protocol registration may require a .desktop file
  • Behavior varies by desktop environment

Security

When handling deep links, validate and sanitize URL inputs before acting on them.

  • Never execute arbitrary code from deep link URLs
  • Validate URL schemes before processing
  • Sanitize user input from URL parameters
  • Use HTTPS for OAuth callbacks when possible

License

MIT