web-launch-kit

Launches external apps and communication intents from the web — deep links and custom schemes, Android intent:// URLs, iOS universal links, store fallbacks, tel / sms / mailto, a file picker and system-settings panes. Every button below performs a real launch on this device.

$ npm install web-launch-kit

Your environment

Which launch routes this engine can take, decided before anything is attempted. debug pauses on a debugger statement before each attempt, which also restores the user gesture when you resume — that is what makes launching from the console work.

readonly version: string;
get debug(): boolean;
set debug(value: boolean);
readonly utils: LaunchKitUtils;

interface LaunchKitUtils {
    get canOpenIntent(): boolean;
    get canOpenUniversal(): boolean;
    get canOpenSetting(): boolean;

    getTrackId(bundleId: string): Promise<string | undefined>;

    getProductId(packageFamilyName: string): Promise<string | undefined>;
}

app(options?)

Every candidate for this OS is tried in order until one sticks, and the promise resolves with the route that opened the app. When they all miss it rejects with a LaunchError carrying the code and the full attempt list, both shown below.

app(options?: AppOpenOptions): Promise<AppOpenedBy>;

type AppOpenedBy = 'scheme' | 'universal' | 'intent' | 'fallback' | 'store';
type LaunchErrorCode = 'unsupported-os' | 'no-candidates' | 'all-failed';
type URLCandidate = URL | string;
type URLCandidateOrFallback = URLCandidate | (() => any);

interface AppOpenOptions {
    android?: AndroidAppInfo;
    ios?: IOSAppInfo;
    windows?: WindowsAppInfo;
    macos?: MacOSAppInfo;

    onAttempt?: (attempt: LaunchAttempt) => void;
}

interface AppInfo {
    scheme?: URLCandidate;
    fallback?: URLCandidateOrFallback;
    timeout?: number;
    allowAppStore?: boolean;
    allowWebStore?: boolean;
}

interface AndroidAppInfo extends AppInfo {
    intent?: URLCandidate;
    packageName?: string;
    allowIntent?: boolean;
    assumeAllowedInApp?: boolean;
}

interface IOSAppInfo extends AppInfo {
    universal?: URLCandidate;
    bundleId?: string;
    trackId?: string;
    assumeAllowedInApp?: boolean;
}

interface WindowsAppInfo extends AppInfo {
    packageFamilyName?: string;
    productId?: string;
}

interface MacOSAppInfo extends AppInfo {
    bundleId?: string;
    trackId?: string;
}

interface LaunchAttempt {
    readonly by: AppOpenedBy;
    readonly url: string;
    readonly index: number;
    readonly total: number;
}

interface LaunchError extends Error {
    readonly code: LaunchErrorCode;
    readonly attempted: readonly LaunchAttempt[];
}
store listing for these identifiers

All four entries are submitted and the kit uses the one matching this device. A function fallback keeps you here instead of navigating, so a route that gives up stays visible in the log. Each sample fills every tab with values that platform actually resolves: Instagram · YouTube · nothing installed

the options object being passed
onAttempt log and result

map(options?)

Opens the native map app for this OS — Apple Maps, Google Maps or the Windows maps handler — and falls back to Google Maps on the web when none of them answer.

map(options?: MapOptions): Promise<AppOpenedBy>;

type URLStringOrFallback = string | (() => any);

interface MapOptions {
    query?: string;
    coordinate?: [number, number];
    label?: string;
    directions?: {
        destination: string | [number, number];
        origin?: string | [number, number];
    };
    zoom?: number;
    fallback?: URLStringOrFallback;

    onAttempt?: (attempt: LaunchAttempt) => void;
}

telephone / message / mail

Composer intents. Recipients and bodies are encoded for you, and the array forms join multiple addresses the way each scheme expects.

telephone(options?: TelephoneOptions): Promise<void>;

message(options?: MessageOptions): Promise<void>;

mail(options?: MailOptions): Promise<void>;

interface TelephoneOptions {
    to?: string;
}

interface MessageOptions {
    to?: string | string[];
    body?: string;
}

interface MailOptions {
    to?: string | string[];
    cc?: string | string[];
    bcc?: string | string[];
    subject?: string;
    body?: string;
}

filepicker(options?)

Uses the File System Access API where it exists and a hidden <input> everywhere else, resolving to the same File[] either way. id and startIn only mean something to the native picker.

filepicker(options?: FilepickerOptions): Promise<File[]>;

interface FilepickerOptions {
    accept?: string | string[];
    id?: string;
    directory?: boolean;
    multiple?: boolean;
    startIn?: OpenPickerStartIn;
}

type OpenPickerStartIn = 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos';

setting(type?)

Opens a settings pane where the platform exposes one. utils.canOpenSetting in the first panel tells you whether this engine has a route at all.

readonly SettingType: typeof SettingType;

setting(type?: SettingType): Promise<void>;

enum SettingType {
    General = 'general',
    Network = 'network',
    Display = 'display',
    Appearance = 'appearance',
    Accessibility = 'accessibility',
    Battery = 'battery',
    Datetime = 'datetime',
    Language = 'language',
    Accounts = 'accounts',
    Storage = 'storage'
}

utils.getTrackId / utils.getProductId

Store identifiers resolved from a bundle id or a package family name, so a store fallback can be built without hard-coding numbers. Answers are cached per id, so asking twice costs one lookup.

getTrackId(bundleId: string): Promise<string | undefined>;

getProductId(packageFamilyName: string): Promise<string | undefined>;