web-launch-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ ![npm](https://img.shields.io/npm/v/web-launch-kit)
2
+ ![bundle size](https://img.shields.io/bundlephobia/minzip/web-launch-kit)
3
+ ![types](https://img.shields.io/npm/types/web-launch-kit)
4
+
5
+ # web-launch-kit
6
+
7
+ A TypeScript library for launching **external apps** and **communication intents**
8
+ from the web. Handles deep links / custom schemes, Android `intent://` URLs, iOS
9
+ universal links, and App Store / web-store fallbacks — trying each candidate in
10
+ order until one succeeds. Also covers `tel`, `sms`, `mailto`, a file picker, and
11
+ system-settings deep links.
12
+
13
+ ```bash
14
+ npm install web-launch-kit
15
+ ```
16
+
17
+ The bundle is self-contained (OS/locale detection is inlined) — no peer scripts required.
18
+
19
+ ## API at a glance
20
+
21
+ `LaunchKit` is a singleton.
22
+
23
+ | Member | Signature | Description |
24
+ | --- | --- | --- |
25
+ | `LaunchKit.version` | `string` | The installed package version |
26
+ | `LaunchKit.SettingType` | `enum` | Setting panes: `General`, `Network`, `Display`, `Appearance`, `Accessibility`, `Battery`, `Datetime`, `Language`, `Accounts`, `Storage` |
27
+ | `LaunchKit.app(options?)` | `Promise<AppOpenedBy>` | Launch an app via the best available route; resolves with which route opened it |
28
+ | `LaunchKit.telephone(options?)` | `Promise<void>` | Open the dialer (`tel:`) |
29
+ | `LaunchKit.message(options?)` | `Promise<void>` | Open the SMS composer (`sms:`) |
30
+ | `LaunchKit.mail(options?)` | `Promise<void>` | Open the mail composer (`mailto:`) |
31
+ | `LaunchKit.filepicker(options?)` | `Promise<File[]>` | Pick files or a directory (File System Access API, with input fallback) |
32
+ | `LaunchKit.setting(type?)` | `Promise<void>` | Open a system-settings pane where supported |
33
+ | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getters, plus async `getTrackId` / `getProductId` |
34
+
35
+ `AppOpenedBy` is one of: `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"`.
36
+
37
+ Named exports `getProductId` / `getTrackId` (synchronous store-id lookups) are also available.
38
+
39
+ ---
40
+
41
+ ## Launching an app
42
+
43
+ `app()` takes per-platform options and only acts on the block matching the current
44
+ OS. It builds an ordered list of candidates and tries each until one launches the
45
+ app, resolving with the route that worked.
46
+
47
+ ```mermaid
48
+ flowchart TD
49
+ A([LaunchKit.app called]) --> B{Detect OS via PlatformKit}
50
+ B -->|unknown| E1([Reject: unsupported OS])
51
+ B -->|android| AND
52
+ B -->|ios| IOS
53
+ B -->|windows| WIN
54
+ B -->|macos| MAC
55
+
56
+ subgraph AND["android · resolveOptions"]
57
+ A1{"intent given, but scheme /<br/>packageName / fallback missing?"}
58
+ A1 -->|yes| A2["parseIntentURL:<br/>derive scheme · packageName · fallback"]
59
+ A1 -->|no| A3
60
+ A2 --> A3{"scheme given, but intent missing?"}
61
+ A3 -->|yes| A4["createIntentURL:<br/>scheme + packageName + fallback"]
62
+ A3 -->|no| A5
63
+ A4 --> A5["Priority list:<br/>intent (if canOpenIntent) ⭢ scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by packageName)"]
64
+ end
65
+
66
+ subgraph IOS["ios · resolveOptions"]
67
+ I1{"bundleId given, but trackId missing?"}
68
+ I1 -->|yes| I2["getTrackId:<br/>iTunes lookup API (bundleId ⭢ trackId)"]
69
+ I1 -->|no| I3
70
+ I2 --> I3["Priority list:<br/>universal (if iOS ≥ 9) ⭢ scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by trackId)"]
71
+ end
72
+
73
+ subgraph WIN["windows · resolveOptions"]
74
+ W1{"packageFamilyName given,<br/>but productId missing?"}
75
+ W1 -->|yes| W2["getProductId:<br/>packageFamilyName ⭢ productId"]
76
+ W1 -->|no| W3
77
+ W2 --> W3["Priority list:<br/>scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by productId)"]
78
+ end
79
+
80
+ subgraph MAC["macos · resolveOptions"]
81
+ M1{"bundleId given, but trackId missing?"}
82
+ M1 -->|yes| M2["getTrackId:<br/>iTunes lookup API (bundleId ⭢ trackId)"]
83
+ M1 -->|no| M3
84
+ M2 --> M3["Priority list:<br/>scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by trackId)"]
85
+ end
86
+
87
+ AND --> P
88
+ IOS --> P
89
+ WIN --> P
90
+ MAC --> P
91
+
92
+ P{"Any URL candidates?"} -->|no| E2([Reject: no openable URL candidates])
93
+ P -->|yes| Q{"Next candidate type?"}
94
+ Q -->|"function fallback"| R["Invoke fallback function"] --> G
95
+ Q -->|"URL string"| O["openURL(index, url, timeout)"]
96
+ O --> F{Opened?}
97
+ F -->|yes| G(["Resolve AppOpenedBy:<br/>'intent' · 'universal' · 'scheme' · 'fallback' · 'store'"])
98
+ F -->|no| H{"Candidates remaining?"}
99
+ H -->|yes| Q
100
+ H -->|no| E3(["Reject: all attempted URLs failed<br/>(error lists every tried URL)"])
101
+
102
+ subgraph openURL["openURL · app-switch detection"]
103
+ T0["Register blur + visibilitychange listeners"] --> T1{"Document focused?"}
104
+ T1 -->|no| T2["restoreFocus:<br/>window ⭢ body ⭢ hidden input"]
105
+ T1 -->|yes| T3
106
+ T2 --> T3{"Environment?"}
107
+ T3 -->|cordova| T4["InAppBrowser.open / window.open ('_system')"]
108
+ T3 -->|browser| T5{"userActivation active<br/>or first attempt?"}
109
+ T5 -->|yes| T6["top.location.href = url"]
110
+ T5 -->|no| T7["Hidden anchor + synthetic click"]
111
+ T6 --> T8["+ hidden iframe (removed after 500ms)"]
112
+ T7 --> T8
113
+ T4 --> T9
114
+ T8 --> T9{"blur / hidden fired?"}
115
+ T9 -->|"yes ⭢ wait focus"| T10([resolve: app opened])
116
+ T9 -->|"no, until timeout"| T11([reject: app not detected])
117
+ end
118
+
119
+ O -.->|delegates to| T0
120
+ ```
121
+
122
+ ```js
123
+ import LaunchKit from 'web-launch-kit'
124
+
125
+ const openedBy = await LaunchKit.app({
126
+ android: {
127
+ scheme: 'ms-excel://',
128
+ packageName: 'com.microsoft.office.excel',
129
+ intent: 'intent://#Intent;scheme=ms-excel;package=com.microsoft.office.excel;action=android.intent.action.VIEW;category=android.intent.category.BROWSABLE;S.browser_fallback_url=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dcom.microsoft.office.excel;end',
130
+ fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
131
+ allowAppStore: true,
132
+ allowWebStore: false,
133
+ timeout: 1000,
134
+ },
135
+ ios: {
136
+ scheme: 'ms-excel://',
137
+ bundleId: 'com.microsoft.Office.Excel',
138
+ trackId: '586683407',
139
+ universal: 'https://1drv.ms/x/c/7f3d9a02c81b4e65/IQBk2wYfN8pTQ5vHmR9xLzUcAeXtP0jWnK4oD3iFgZs7bQY?e=Rk9mZ2',
140
+ fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
141
+ allowAppStore: true,
142
+ allowWebStore: false,
143
+ timeout: 2000,
144
+ },
145
+ windows: {
146
+ scheme: 'ms-excel://',
147
+ packageFamilyName: 'Microsoft.Office.Desktop_8wekyb3d8bbwe',
148
+ productId: 'cfq7ttc0pr28',
149
+ fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
150
+ allowAppStore: true,
151
+ allowWebStore: false,
152
+ timeout: 750,
153
+ },
154
+ macos: {
155
+ scheme: 'ms-excel://',
156
+ bundleId: 'com.microsoft.Excel',
157
+ trackId: '462058435',
158
+ fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
159
+ allowAppStore: true,
160
+ allowWebStore: false,
161
+ timeout: 750,
162
+ }
163
+ })
164
+
165
+ console.log(openedBy) // "universal" | "scheme" | "intent" | "fallback" | "store"
166
+ ```
167
+
168
+ Per-platform fields: Android accepts `intent` / `scheme` / `packageName` / `fallback`
169
+ (scheme ⇄ intent are derived from each other); iOS accepts `universal` / `scheme` /
170
+ `bundleId` / `trackId`; Windows accepts `scheme` / `packageFamilyName` / `productId`;
171
+ macOS accepts `scheme` / `bundleId` / `trackId`. All accept `fallback`, `timeout`,
172
+ `allowAppStore`, `allowWebStore`.
173
+
174
+ ## Communication intents
175
+
176
+ ```js
177
+ import LaunchKit from 'web-launch-kit'
178
+
179
+ await LaunchKit.telephone({ to: '+821012345678' })
180
+
181
+ await LaunchKit.message({ to: '+821012345678', body: 'hello' })
182
+
183
+ await LaunchKit.mail({
184
+ to: ['a@example.com', 'b@example.com'],
185
+ cc: 'c@example.com',
186
+ subject: 'Hi',
187
+ body: 'from web-launch-kit',
188
+ })
189
+ ```
190
+
191
+ ## File picker
192
+
193
+ ```js
194
+ import LaunchKit from 'web-launch-kit'
195
+
196
+ // Files (uses showOpenFilePicker where available, falls back to <input type=file>)
197
+ const files = await LaunchKit.filepicker({ accept: ['image/*', '.pdf'], multiple: true })
198
+
199
+ // A directory (recursive; webkitRelativePath is populated)
200
+ const tree = await LaunchKit.filepicker({ directory: true })
201
+ ```
202
+
203
+ ## System settings
204
+
205
+ ```js
206
+ import LaunchKit from 'web-launch-kit'
207
+
208
+ if (LaunchKit.utils.canOpenSetting) {
209
+ await LaunchKit.setting(LaunchKit.SettingType.Network)
210
+ }
211
+ ```
212
+
213
+ ## CommonJS / UMD
214
+
215
+ The bundle is built with `exports: "named"`, so the singleton lives under `.default`:
216
+
217
+ ```js
218
+ const { default: LaunchKit } = require('web-launch-kit')
219
+ ```
220
+
221
+ ```html
222
+ <script src="https://unpkg.com/web-launch-kit/dist/launch-kit.umd.min.js"></script>
223
+ <script>
224
+ window.LaunchKit.default.telephone({ to: '+821012345678' })
225
+ </script>
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Notes
231
+
232
+ - **Deep links need a real user gesture.** Universal links and custom schemes are
233
+ unreliable when triggered programmatically or from a same-origin context — they
234
+ fall back to the web instead of opening the app. Call `app()` from a click/tap handler.
235
+ - **`app()` resolves with the route, not a guarantee of launch.** Detection relies on
236
+ focus/visibility heuristics with per-OS timeouts; a resolved `AppOpenedBy` means that
237
+ candidate was attempted and the page appeared to background, not a hard confirmation.
238
+ - **`utils.getTrackId` / `getProductId` are async**; the named `getTrackId` / `getProductId`
239
+ exports are synchronous (blocking XHR) and intended for internal/legacy use.
240
+ - **Store-id lookups depend on remote APIs** (iTunes Lookup, Microsoft display catalog)
241
+ and are cached for one hour; failures resolve to `undefined` rather than throwing.
@@ -0,0 +1,147 @@
1
+ declare global {
2
+ interface Document {
3
+ webkitVisibilityState?: 'hidden' | 'visible';
4
+ mozVisibilityState?: 'hidden' | 'visible';
5
+ msVisibilityState?: 'hidden' | 'visible';
6
+ webkitHidden?: boolean;
7
+ mozHidden?: boolean;
8
+ msHidden?: boolean;
9
+ }
10
+ interface FileSystemDirectoryHandle {
11
+ values(): AsyncIterableIterator<FileSystemHandle>;
12
+ }
13
+ interface SymbolConstructor {
14
+ readonly asyncIterator: symbol;
15
+ }
16
+ var showOpenFilePicker: (options?: OpenFilePickerOptions) => Promise<FileSystemFileHandle[]>;
17
+ var showDirectoryPicker: (options?: OpenDirectoryPickerOptions) => Promise<FileSystemDirectoryHandle>;
18
+ var cordova: CordovaPlugin | undefined;
19
+ }
20
+ interface OpenFilePickerOptions {
21
+ excludeAcceptAllOption?: boolean;
22
+ id?: string;
23
+ multiple?: boolean;
24
+ startIn?: OpenPickerStartIn;
25
+ types?: {
26
+ description?: string;
27
+ accept: Record<string, string[]>;
28
+ }[];
29
+ }
30
+ interface OpenDirectoryPickerOptions {
31
+ id?: string;
32
+ mode?: 'read' | 'readwrite';
33
+ startIn?: OpenPickerStartIn;
34
+ }
35
+ interface IteratorYieldResult<TYield> {
36
+ done?: false;
37
+ value: TYield;
38
+ }
39
+ interface IteratorReturnResult<TReturn> {
40
+ done: true;
41
+ value: TReturn;
42
+ }
43
+ interface AsyncIterator<T, TReturn = any, TNext = any> {
44
+ next(...[value]: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
45
+ return?(value?: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
46
+ throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
47
+ }
48
+ interface AsyncIterableIterator<T, TReturn = any, TNext = any> extends AsyncIterator<T, TReturn, TNext> {
49
+ [Symbol.asyncIterator](): AsyncIterableIterator<T, TReturn, TNext>;
50
+ }
51
+ type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
52
+ declare type URLCandidate = URL | string;
53
+ declare type URLCandidateOrFallback = URLCandidate | (() => any);
54
+ declare type URLStringOrFallback = string | (() => any);
55
+ declare type OpenPickerStartIn = 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos';
56
+ declare type AppOpenedBy = 'scheme' | 'universal' | 'intent' | 'fallback' | 'store';
57
+ declare enum SettingType {
58
+ General = "general",
59
+ Network = "network",
60
+ Display = "display",
61
+ Appearance = "appearance",
62
+ Accessibility = "accessibility",
63
+ Battery = "battery",
64
+ Datetime = "datetime",
65
+ Language = "language",
66
+ Accounts = "accounts",
67
+ Storage = "storage"
68
+ }
69
+ interface CordovaPlugin {
70
+ InAppBrowser?: CordovaInAppBrowser;
71
+ }
72
+ interface CordovaInAppBrowser {
73
+ open(url?: string | URL, target?: string, features?: string): WindowProxy | null;
74
+ }
75
+ declare interface AppInfo {
76
+ scheme?: URLCandidate;
77
+ fallback?: URLCandidateOrFallback;
78
+ timeout?: number;
79
+ allowAppStore?: boolean;
80
+ allowWebStore?: boolean;
81
+ }
82
+ declare interface AndroidAppInfo extends AppInfo {
83
+ intent?: URLCandidate;
84
+ packageName?: string;
85
+ }
86
+ declare interface IOSAppInfo extends AppInfo {
87
+ universal?: URLCandidate;
88
+ bundleId?: string;
89
+ trackId?: string;
90
+ }
91
+ declare interface WindowsAppInfo extends AppInfo {
92
+ packageFamilyName?: string;
93
+ productId?: string;
94
+ }
95
+ declare interface MacOSAppInfo extends AppInfo {
96
+ bundleId?: string;
97
+ trackId?: string;
98
+ }
99
+ declare interface AppOpenOptions {
100
+ android?: AndroidAppInfo;
101
+ ios?: IOSAppInfo;
102
+ windows?: WindowsAppInfo;
103
+ macos?: MacOSAppInfo;
104
+ }
105
+ declare interface TelephoneOptions {
106
+ to?: string;
107
+ }
108
+ declare interface MessageOptions {
109
+ to?: string | string[];
110
+ body?: string;
111
+ }
112
+ declare interface MailOptions {
113
+ to?: string | string[];
114
+ cc?: string | string[];
115
+ bcc?: string | string[];
116
+ subject?: string;
117
+ body?: string;
118
+ }
119
+ declare interface FilepickerOptions {
120
+ accept?: string | string[];
121
+ id?: string;
122
+ directory?: boolean;
123
+ multiple?: boolean;
124
+ startIn?: OpenPickerStartIn;
125
+ }
126
+ interface LaunchKitUtils {
127
+ get canOpenIntent(): boolean;
128
+ get canOpenUniversal(): boolean;
129
+ get canOpenSetting(): boolean;
130
+ getTrackId(bundleId: string): Promise<string | undefined>;
131
+ getProductId(packageFamilyName: string): Promise<string | undefined>;
132
+ }
133
+ interface LaunchKitInstance {
134
+ readonly version: string;
135
+ readonly SettingType: typeof SettingType;
136
+ readonly utils: LaunchKitUtils;
137
+ app(options?: AppOpenOptions): Promise<AppOpenedBy>;
138
+ telephone(options?: TelephoneOptions): Promise<void>;
139
+ message(options?: MessageOptions): Promise<void>;
140
+ mail(options?: MailOptions): Promise<void>;
141
+ filepicker(options?: FilepickerOptions): Promise<File[]>;
142
+ setting(type?: SettingType): Promise<void>;
143
+ }
144
+ declare const LaunchKit: LaunchKitInstance;
145
+
146
+ export { SettingType, LaunchKit as default };
147
+ export type { AndroidAppInfo, AppInfo, AppOpenOptions, AppOpenedBy, FilepickerOptions, IOSAppInfo, LaunchKitInstance, MacOSAppInfo, MailOptions, MessageOptions, OpenPickerStartIn, TelephoneOptions, URLCandidate, URLCandidateOrFallback, URLStringOrFallback, WindowsAppInfo };