web-launch-kit 0.0.5 → 0.0.7
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.ko.md +45 -8
- package/README.md +47 -8
- package/dist/index.d.ts +16 -2
- package/dist/launch-kit.cjs +109 -71
- package/dist/launch-kit.cjs.map +1 -1
- package/dist/launch-kit.min.cjs +1 -1
- package/dist/launch-kit.min.cjs.map +1 -1
- package/dist/launch-kit.min.mjs +1 -1
- package/dist/launch-kit.min.mjs.map +1 -1
- package/dist/launch-kit.mjs +109 -71
- package/dist/launch-kit.mjs.map +1 -1
- package/dist/launch-kit.umd.js +109 -71
- package/dist/launch-kit.umd.js.map +1 -1
- package/dist/launch-kit.umd.min.js +1 -1
- package/dist/launch-kit.umd.min.js.map +1 -1
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -30,13 +30,20 @@ npm install web-launch-kit
|
|
|
30
30
|
| `LaunchKit.telephone(options?)` | `Promise<void>` | 전화 앱 열기 (`tel:`) |
|
|
31
31
|
| `LaunchKit.message(options?)` | `Promise<void>` | 문자 작성 열기 (`sms:`) |
|
|
32
32
|
| `LaunchKit.mail(options?)` | `Promise<void>` | 메일 작성 열기 (`mailto:`) |
|
|
33
|
-
| `LaunchKit.map(options?)` | `Promise<
|
|
33
|
+
| `LaunchKit.map(options?)` | `Promise<AppOpenedBy>` | 검색어·좌표·경로로 지도 열기. OS별 네이티브 앱과 Google Maps 웹 폴백 |
|
|
34
34
|
| `LaunchKit.filepicker(options?)` | `Promise<File[]>` | 파일 또는 디렉터리 선택 (File System Access API, input 폴백) |
|
|
35
35
|
| `LaunchKit.setting(type?)` | `Promise<void>` | 지원되는 환경에서 시스템 설정 화면 열기 |
|
|
36
36
|
| `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getter와 비동기 `getTrackId` / `getProductId` |
|
|
37
37
|
|
|
38
38
|
`AppOpenedBy`는 `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"` 중 하나입니다.
|
|
39
39
|
|
|
40
|
+
`app()`과 `map()`은 `LaunchError`로 reject합니다 — 필드 두 개가 붙은 실제 `Error`입니다.
|
|
41
|
+
|
|
42
|
+
| 필드 | 타입 | 설명 |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `code` | `"unsupported-os" \| "no-candidates" \| "all-failed"` | 실패 사유 |
|
|
45
|
+
| `attempted` | `readonly LaunchAttempt[]` | 시도한 모든 후보의 `{ by, url, index, total }` |
|
|
46
|
+
|
|
40
47
|
---
|
|
41
48
|
|
|
42
49
|
## ESM
|
|
@@ -112,10 +119,10 @@ flowchart TD
|
|
|
112
119
|
A1{"intent given, but scheme /<br/>packageName / fallback missing?"}
|
|
113
120
|
A1 -->|yes| A2["parseIntentURL:<br/>derive scheme · packageName · fallback"]
|
|
114
121
|
A1 -->|no| A3
|
|
115
|
-
A2 --> A3{"scheme given
|
|
122
|
+
A2 --> A3{"allowIntent, and scheme given<br/>but intent missing?"}
|
|
116
123
|
A3 -->|yes| A4["createIntentURL:<br/>scheme + packageName + fallback"]
|
|
117
124
|
A3 -->|no| A5
|
|
118
|
-
A4 --> A5["Priority list:<br/>intent (if canOpenIntent) ⭢ scheme (if canOpenScheme)<br/>⭢ fallback ⭢ app store (if canOpenScheme) ⭢ web store"]
|
|
125
|
+
A4 --> A5["Priority list:<br/>intent (if allowIntent and canOpenIntent) ⭢ scheme (if canOpenScheme)<br/>⭢ fallback ⭢ app store (if canOpenScheme) ⭢ web store"]
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
subgraph IOS["ios · resolveOptions"]
|
|
@@ -195,11 +202,26 @@ const openedBy = await LaunchKit.app({
|
|
|
195
202
|
console.log(openedBy) // "universal" | "scheme" | "intent" | "fallback" | "store"
|
|
196
203
|
```
|
|
197
204
|
|
|
198
|
-
플랫폼별 필드: 안드로이드는 `intent` / `scheme` / `packageName` / `fallback`
|
|
199
|
-
서로에게서 유도됩니다), iOS는 `universal` / `scheme` / `bundleId` / `trackId`, Windows는
|
|
205
|
+
플랫폼별 필드: 안드로이드는 `intent` / `scheme` / `packageName` / `fallback` /
|
|
206
|
+
`allowIntent`(scheme ⇄ intent는 서로에게서 유도됩니다), iOS는 `universal` / `scheme` / `bundleId` / `trackId`, Windows는
|
|
200
207
|
`scheme` / `packageFamilyName` / `productId`, macOS는 `scheme` / `bundleId` / `trackId`를
|
|
201
208
|
받습니다. 모두 `fallback`, `timeout`, `allowAppStore`, `allowWebStore`를 함께 받습니다.
|
|
202
209
|
|
|
210
|
+
`onAttempt`는 각 후보 시도 직전에 동기로 호출됩니다. 대기 UI를 여기서 그리세요 — 그러지
|
|
211
|
+
않으면 타임아웃 동안 화면에 아무 변화가 없습니다.
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
await LaunchKit.app({
|
|
215
|
+
android: { scheme: 'myapp://profile/42', packageName: 'com.example.myapp', allowWebStore: true },
|
|
216
|
+
onAttempt({ by, index, total }) {
|
|
217
|
+
setStatus(by === 'store' ? '스토어로 이동합니다' : '앱을 여는 중… (' + (index + 1) + '/' + total + ')')
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
이미 진행 중인 호출이 있으면 **같은 프로미스를 반환**합니다. 연타해도 두 번째 체인이 첫 번째
|
|
223
|
+
내비게이션을 덮어쓰는 일이 없습니다.
|
|
224
|
+
|
|
203
225
|
안드로이드와 iOS는 `assumeAllowedInApp`을 추가로 받습니다. 파트너 화이트리스트로 동작하는
|
|
204
226
|
인앱 브라우저(예: 웨이보)에 앱이 등록되어 있을 때만 `true`로 선언하세요. 그러면 그 환경에서
|
|
205
227
|
scheme·유니버설 링크 후보가 유지됩니다. 완전히 차단된 웹뷰나 OS 버전 요구사항은 이 옵션으로
|
|
@@ -234,6 +256,9 @@ const files = await LaunchKit.filepicker({ accept: ['image/*', '.pdf'], multiple
|
|
|
234
256
|
const tree = await LaunchKit.filepicker({ directory: true })
|
|
235
257
|
```
|
|
236
258
|
|
|
259
|
+
취소하면 `[]`로 resolve합니다. `cancel` 이벤트가 있는 엔진은 즉시 알려주고, 나머지는 값이 빈
|
|
260
|
+
상태로 포커스가 돌아오는 것으로 판별하므로 조금 더 걸립니다.
|
|
261
|
+
|
|
237
262
|
## 지도
|
|
238
263
|
|
|
239
264
|
`map()`은 검색어·좌표·경로 중 하나를 받아 현재 OS에 맞는 URL을 만듭니다
|
|
@@ -298,15 +323,27 @@ if (LaunchKit.utils.canOpenSetting) {
|
|
|
298
323
|
- **`app()`이 반환하는 것은 경로이지 실행 보증이 아닙니다.** 감지는 OS별 타임아웃을 둔
|
|
299
324
|
포커스·가시성 휴리스틱에 기반합니다. `AppOpenedBy`가 반환됐다는 건 그 후보가 시도되었고
|
|
300
325
|
페이지가 백그라운드로 전환된 것처럼 보였다는 뜻이지, 확정된 실행 확인은 아닙니다.
|
|
301
|
-
- **`utils.getTrackId` / `getProductId`는 비동기**이고
|
|
302
|
-
|
|
326
|
+
- **`utils.getTrackId` / `getProductId`는 비동기**이고 1시간 캐시됩니다. 페이지 초기화 때
|
|
327
|
+
한 번 호출해두면 `app()`이 id를 인라인으로 조회하지 않습니다. `app()`은 사용자 제스처 안에서
|
|
328
|
+
실행돼야 하므로, id가 없으면 `await`으로 user activation을 잃는 대신 동기로 조회합니다.
|
|
329
|
+
`trackId` / `productId`를 직접 주면 조회 자체가 생략됩니다.
|
|
303
330
|
- **스토어 id 조회는 외부 API에 의존합니다**(iTunes Lookup, Microsoft display catalog).
|
|
304
331
|
1시간 캐시되며 실패는 예외가 아니라 `undefined`로 resolve됩니다.
|
|
305
332
|
- **인앱 브라우저는 후보 단위로 걸러집니다.** 실행이 차단된 웹뷰(WeChat, QQ, Qzone, Baidu,
|
|
306
333
|
그리고 파트너가 아닌 웨이보) 안에서는 커스텀 스킴·`intent://`·`market://` 같은 스토어 스킴이
|
|
307
334
|
타임아웃을 소진하지 않고 미리 제외됩니다. 그래서 체인이 곧바로 `fallback` / 웹스토어로
|
|
308
335
|
떨어집니다. 화이트리스트에 등록된 파트너 앱은 `assumeAllowedInApp: true`로 되돌립니다.
|
|
309
|
-
- **`
|
|
336
|
+
- **`allowIntent: false`는 안드로이드의 `intent://` 경로를 제외합니다.** 유도된 `intent://`에는
|
|
337
|
+
`S.browser_fallback_url`이 실립니다 — `fallback`에 URL을 주면 그 URL, 아니면 `packageName`이
|
|
338
|
+
있을 때 스토어입니다. 그래서 그 이동은 브라우저가 첫 시도 안에서 직접 처리합니다. 앱이 없는
|
|
339
|
+
사용자를 스토어로 보내는 가장 빠른 경로지만, 동시에 첫 시도가 항상 어딘가에 도달하므로 그
|
|
340
|
+
뒤의 순수 스킴은 실행되지 않습니다. `allowIntent: false`로 이 경로를 아예 건너뛰면 순서를
|
|
341
|
+
체인이 결정합니다 — 스킴, `fallback`, 그리고 허용한 스토어 순입니다. 직접 넘긴 `intent`도 같이
|
|
342
|
+
제외되지만, 거기서 파싱한 scheme·`packageName`·`fallback`은 그대로 사용됩니다.
|
|
343
|
+
- **`map()`은 각 후보에 OS별 타임아웃**을 사용하고, `app()`처럼 성공한 경로를 반환합니다.
|
|
344
|
+
- **웹 폴백은 내비게이션 시점에 resolve합니다.** `fallback`이나 웹스토어 후보는 문서 자체를
|
|
345
|
+
교체하므로 기다릴 앱 전환이 없습니다. 이동을 시작하는 즉시 resolve하며, 이동이 성공한 뒤에
|
|
346
|
+
reject되는 일이 없습니다.
|
|
310
347
|
|
|
311
348
|
## 브라우저 지원
|
|
312
349
|
|
package/README.md
CHANGED
|
@@ -31,13 +31,20 @@ The bundle is self-contained (OS/locale detection is inlined) — no peer script
|
|
|
31
31
|
| `LaunchKit.telephone(options?)` | `Promise<void>` | Open the dialer (`tel:`) |
|
|
32
32
|
| `LaunchKit.message(options?)` | `Promise<void>` | Open the SMS composer (`sms:`) |
|
|
33
33
|
| `LaunchKit.mail(options?)` | `Promise<void>` | Open the mail composer (`mailto:`) |
|
|
34
|
-
| `LaunchKit.map(options?)` | `Promise<
|
|
34
|
+
| `LaunchKit.map(options?)` | `Promise<AppOpenedBy>` | Open a map by query, coordinate, or directions; native app per-OS with a Google Maps web fallback |
|
|
35
35
|
| `LaunchKit.filepicker(options?)` | `Promise<File[]>` | Pick files or a directory (File System Access API, with input fallback) |
|
|
36
36
|
| `LaunchKit.setting(type?)` | `Promise<void>` | Open a system-settings pane where supported |
|
|
37
37
|
| `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getters, plus async `getTrackId` / `getProductId` |
|
|
38
38
|
|
|
39
39
|
`AppOpenedBy` is one of: `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"`.
|
|
40
40
|
|
|
41
|
+
`app()` and `map()` reject with a `LaunchError` — a real `Error` carrying two extra fields:
|
|
42
|
+
|
|
43
|
+
| Field | Type | Description |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `code` | `"unsupported-os" \| "no-candidates" \| "all-failed"` | Why it failed |
|
|
46
|
+
| `attempted` | `readonly LaunchAttempt[]` | `{ by, url, index, total }` for every candidate tried |
|
|
47
|
+
|
|
41
48
|
---
|
|
42
49
|
|
|
43
50
|
## ESM
|
|
@@ -113,10 +120,10 @@ flowchart TD
|
|
|
113
120
|
A1{"intent given, but scheme /<br/>packageName / fallback missing?"}
|
|
114
121
|
A1 -->|yes| A2["parseIntentURL:<br/>derive scheme · packageName · fallback"]
|
|
115
122
|
A1 -->|no| A3
|
|
116
|
-
A2 --> A3{"scheme given
|
|
123
|
+
A2 --> A3{"allowIntent, and scheme given<br/>but intent missing?"}
|
|
117
124
|
A3 -->|yes| A4["createIntentURL:<br/>scheme + packageName + fallback"]
|
|
118
125
|
A3 -->|no| A5
|
|
119
|
-
A4 --> A5["Priority list:<br/>intent (if canOpenIntent) ⭢ scheme (if canOpenScheme)<br/>⭢ fallback ⭢ app store (if canOpenScheme) ⭢ web store"]
|
|
126
|
+
A4 --> A5["Priority list:<br/>intent (if allowIntent and canOpenIntent) ⭢ scheme (if canOpenScheme)<br/>⭢ fallback ⭢ app store (if canOpenScheme) ⭢ web store"]
|
|
120
127
|
end
|
|
121
128
|
|
|
122
129
|
subgraph IOS["ios · resolveOptions"]
|
|
@@ -196,12 +203,27 @@ const openedBy = await LaunchKit.app({
|
|
|
196
203
|
console.log(openedBy) // "universal" | "scheme" | "intent" | "fallback" | "store"
|
|
197
204
|
```
|
|
198
205
|
|
|
199
|
-
Per-platform fields: Android accepts `intent` / `scheme` / `packageName` / `fallback`
|
|
200
|
-
(scheme ⇄ intent are derived from each other); iOS accepts `universal` / `scheme` /
|
|
206
|
+
Per-platform fields: Android accepts `intent` / `scheme` / `packageName` / `fallback` /
|
|
207
|
+
`allowIntent` (scheme ⇄ intent are derived from each other); iOS accepts `universal` / `scheme` /
|
|
201
208
|
`bundleId` / `trackId`; Windows accepts `scheme` / `packageFamilyName` / `productId`;
|
|
202
209
|
macOS accepts `scheme` / `bundleId` / `trackId`. All accept `fallback`, `timeout`,
|
|
203
210
|
`allowAppStore`, `allowWebStore`.
|
|
204
211
|
|
|
212
|
+
`onAttempt` fires synchronously before each candidate, which is where you drive the waiting
|
|
213
|
+
UI — there is otherwise nothing on screen for the length of the timeout.
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
await LaunchKit.app({
|
|
217
|
+
android: { scheme: 'myapp://profile/42', packageName: 'com.example.myapp', allowWebStore: true },
|
|
218
|
+
onAttempt({ by, index, total }) {
|
|
219
|
+
setStatus(by === 'store' ? 'Go to the store' : 'Opening the app… (' + (index + 1) + '/' + total + ')')
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
A second call while one is still running returns the same promise, so a double tap cannot
|
|
225
|
+
start a second chain that navigates over the first.
|
|
226
|
+
|
|
205
227
|
Android and iOS additionally accept
|
|
206
228
|
`assumeAllowedInApp` — declare it `true` only when your app is whitelisted by a
|
|
207
229
|
partner-gated in-app browser (e.g. Weibo) so scheme / universal-link candidates are
|
|
@@ -236,6 +258,9 @@ const files = await LaunchKit.filepicker({ accept: ['image/*', '.pdf'], multiple
|
|
|
236
258
|
const tree = await LaunchKit.filepicker({ directory: true })
|
|
237
259
|
```
|
|
238
260
|
|
|
261
|
+
Cancelling resolves with `[]`. Engines with a `cancel` event report it immediately; the rest
|
|
262
|
+
are detected by regaining focus with an empty value, which takes a moment longer.
|
|
263
|
+
|
|
239
264
|
## Maps
|
|
240
265
|
|
|
241
266
|
`map()` takes an intent — a search query, a coordinate, or a route — and builds the
|
|
@@ -301,8 +326,10 @@ if (LaunchKit.utils.canOpenSetting) {
|
|
|
301
326
|
- **`app()` resolves with the route, not a guarantee of launch.** Detection relies on
|
|
302
327
|
focus/visibility heuristics with per-OS timeouts; a resolved `AppOpenedBy` means that
|
|
303
328
|
candidate was attempted and the page appeared to background, not a hard confirmation.
|
|
304
|
-
- **`utils.getTrackId` / `getProductId` are async** and
|
|
305
|
-
|
|
329
|
+
- **`utils.getTrackId` / `getProductId` are async** and cached for an hour, so calling one
|
|
330
|
+
during page setup keeps `app()` from resolving the id inline. `app()` must run inside a
|
|
331
|
+
user gesture, so when the id is missing it looks it up synchronously rather than awaiting
|
|
332
|
+
and losing user activation. Passing `trackId` / `productId` directly skips it entirely.
|
|
306
333
|
- **Store-id lookups depend on remote APIs** (iTunes Lookup, Microsoft display catalog)
|
|
307
334
|
and are cached for one hour; failures resolve to `undefined` rather than throwing.
|
|
308
335
|
- **In-app browsers are gated per candidate.** Inside webviews that block launches
|
|
@@ -311,7 +338,19 @@ if (LaunchKit.utils.canOpenSetting) {
|
|
|
311
338
|
burning their timeouts, so the chain falls straight through to `fallback` / web store
|
|
312
339
|
(the only routes that work there). Whitelisted partner apps opt back in with
|
|
313
340
|
`assumeAllowedInApp: true`.
|
|
314
|
-
- **`
|
|
341
|
+
- **`allowIntent: false` drops the Android `intent://` route.** A derived `intent://` carries
|
|
342
|
+
`S.browser_fallback_url` — your `fallback` when you gave a URL, otherwise the store when a
|
|
343
|
+
`packageName` is present — so the browser handles that hop itself during the first attempt.
|
|
344
|
+
That is the fastest route to the store for a user without the app, but it also means the
|
|
345
|
+
first attempt always lands somewhere, and the plain scheme behind it never runs. Set
|
|
346
|
+
`allowIntent: false` to skip the route entirely and let the chain decide the order: scheme,
|
|
347
|
+
then `fallback`, then the store you allowed. An explicit `intent` is gated too, though the
|
|
348
|
+
scheme, `packageName` and `fallback` parsed out of it are still used.
|
|
349
|
+
- **`map()` uses the per-OS timeout** for each candidate, and resolves with the route that
|
|
350
|
+
worked just like `app()`.
|
|
351
|
+
- **Web fallbacks resolve on navigation.** A `fallback` or web-store candidate replaces the
|
|
352
|
+
document, so there is no app switch to wait for — those resolve as soon as the navigation
|
|
353
|
+
is issued instead of risking a rejection after the move already succeeded.
|
|
315
354
|
|
|
316
355
|
## Browser support
|
|
317
356
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,17 @@ declare type URLCandidateOrFallback = URLCandidate | (() => any);
|
|
|
3
3
|
declare type URLStringOrFallback = string | (() => any);
|
|
4
4
|
declare type OpenPickerStartIn = 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos';
|
|
5
5
|
declare type AppOpenedBy = 'scheme' | 'universal' | 'intent' | 'fallback' | 'store';
|
|
6
|
+
declare type LaunchErrorCode = 'unsupported-os' | 'no-candidates' | 'all-failed';
|
|
7
|
+
declare interface LaunchAttempt {
|
|
8
|
+
readonly by: AppOpenedBy;
|
|
9
|
+
readonly url: string;
|
|
10
|
+
readonly index: number;
|
|
11
|
+
readonly total: number;
|
|
12
|
+
}
|
|
13
|
+
declare interface LaunchError extends Error {
|
|
14
|
+
readonly code: LaunchErrorCode;
|
|
15
|
+
readonly attempted: readonly LaunchAttempt[];
|
|
16
|
+
}
|
|
6
17
|
declare enum SettingType {
|
|
7
18
|
General = "general",
|
|
8
19
|
Network = "network",
|
|
@@ -25,6 +36,7 @@ declare interface AppInfo {
|
|
|
25
36
|
declare interface AndroidAppInfo extends AppInfo {
|
|
26
37
|
intent?: URLCandidate;
|
|
27
38
|
packageName?: string;
|
|
39
|
+
allowIntent?: boolean;
|
|
28
40
|
assumeAllowedInApp?: boolean;
|
|
29
41
|
}
|
|
30
42
|
declare interface IOSAppInfo extends AppInfo {
|
|
@@ -46,6 +58,7 @@ declare interface AppOpenOptions {
|
|
|
46
58
|
ios?: IOSAppInfo;
|
|
47
59
|
windows?: WindowsAppInfo;
|
|
48
60
|
macos?: MacOSAppInfo;
|
|
61
|
+
onAttempt?: (attempt: LaunchAttempt) => void;
|
|
49
62
|
}
|
|
50
63
|
declare interface TelephoneOptions {
|
|
51
64
|
to?: string;
|
|
@@ -78,6 +91,7 @@ declare interface MapOptions {
|
|
|
78
91
|
};
|
|
79
92
|
zoom?: number;
|
|
80
93
|
fallback?: URLStringOrFallback;
|
|
94
|
+
onAttempt?: (attempt: LaunchAttempt) => void;
|
|
81
95
|
}
|
|
82
96
|
interface LaunchKitUtils {
|
|
83
97
|
get canOpenIntent(): boolean;
|
|
@@ -96,11 +110,11 @@ interface LaunchKitInstance {
|
|
|
96
110
|
telephone(options?: TelephoneOptions): Promise<void>;
|
|
97
111
|
message(options?: MessageOptions): Promise<void>;
|
|
98
112
|
mail(options?: MailOptions): Promise<void>;
|
|
99
|
-
map(options?: MapOptions): Promise<
|
|
113
|
+
map(options?: MapOptions): Promise<AppOpenedBy>;
|
|
100
114
|
filepicker(options?: FilepickerOptions): Promise<File[]>;
|
|
101
115
|
setting(type?: SettingType): Promise<void>;
|
|
102
116
|
}
|
|
103
117
|
declare const LaunchKit: LaunchKitInstance;
|
|
104
118
|
|
|
105
119
|
export { SettingType, LaunchKit as default };
|
|
106
|
-
export type { AndroidAppInfo, AppInfo, AppOpenOptions, AppOpenedBy, FilepickerOptions, IOSAppInfo, LaunchKitInstance, MacOSAppInfo, MailOptions, MapOptions, MessageOptions, OpenPickerStartIn, TelephoneOptions,
|
|
120
|
+
export type { AndroidAppInfo, AppInfo, AppOpenOptions, AppOpenedBy, FilepickerOptions, IOSAppInfo, LaunchAttempt, LaunchError, LaunchErrorCode, LaunchKitInstance, MacOSAppInfo, MailOptions, MapOptions, MessageOptions, OpenPickerStartIn, TelephoneOptions, WindowsAppInfo };
|
package/dist/launch-kit.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var version$3 = "0.0.
|
|
5
|
+
var version$3 = "0.0.7";
|
|
6
6
|
var packageJSON$3 = {
|
|
7
7
|
version: version$3};
|
|
8
8
|
|
|
@@ -1898,6 +1898,7 @@ var GLOBAL = getGlobal$1();
|
|
|
1898
1898
|
var NAVIGATOR = GLOBAL.navigator;
|
|
1899
1899
|
var CLEANUP_INPUT_ELEMENT = null;
|
|
1900
1900
|
var DEBUG = false;
|
|
1901
|
+
var APP_IN_FLIGHT = null;
|
|
1901
1902
|
function createTypeGuard(tag) {
|
|
1902
1903
|
return function (value) {
|
|
1903
1904
|
return Object.prototype.toString.call(value) === '[object ' + tag + ']';
|
|
@@ -2114,7 +2115,8 @@ function resolveFocusEventConfig() {
|
|
|
2114
2115
|
};
|
|
2115
2116
|
}
|
|
2116
2117
|
}
|
|
2117
|
-
function openURL(tried, url, timeout) {
|
|
2118
|
+
function openURL(tried, url, timeout, terminal) {
|
|
2119
|
+
if (terminal === void 0) { terminal = false; }
|
|
2118
2120
|
var config = resolveFocusEventConfig();
|
|
2119
2121
|
var top = getTopmostWindow();
|
|
2120
2122
|
var topDocument = top.document;
|
|
@@ -2152,6 +2154,8 @@ function openURL(tried, url, timeout) {
|
|
|
2152
2154
|
}
|
|
2153
2155
|
}
|
|
2154
2156
|
}
|
|
2157
|
+
if (terminal)
|
|
2158
|
+
return;
|
|
2155
2159
|
try {
|
|
2156
2160
|
iframe = createHiddenElement('iframe');
|
|
2157
2161
|
iframe.src = url;
|
|
@@ -2170,6 +2174,17 @@ function openURL(tried, url, timeout) {
|
|
|
2170
2174
|
catch (_) {
|
|
2171
2175
|
}
|
|
2172
2176
|
}
|
|
2177
|
+
if (terminal) {
|
|
2178
|
+
return new Promise(function (resolve, reject) {
|
|
2179
|
+
try {
|
|
2180
|
+
open();
|
|
2181
|
+
resolve();
|
|
2182
|
+
}
|
|
2183
|
+
catch (_) {
|
|
2184
|
+
reject();
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2173
2188
|
return new Promise(function (resolve, reject) {
|
|
2174
2189
|
if (DEBUG)
|
|
2175
2190
|
debugger;
|
|
@@ -2251,11 +2266,78 @@ function openURL(tried, url, timeout) {
|
|
|
2251
2266
|
}
|
|
2252
2267
|
});
|
|
2253
2268
|
}
|
|
2269
|
+
var FUNCTION_FALLBACK_LABEL = '[function fallback]';
|
|
2270
|
+
var CANCEL_DELAY = 1000;
|
|
2271
|
+
function createLaunchError(code, message, attempted) {
|
|
2272
|
+
var error = new Error(message);
|
|
2273
|
+
var holder = error;
|
|
2274
|
+
holder['code'] = code;
|
|
2275
|
+
holder['attempted'] = attempted;
|
|
2276
|
+
return error;
|
|
2277
|
+
}
|
|
2278
|
+
function isWebURL(url) {
|
|
2279
|
+
return /^https?:\/\//i.test(url);
|
|
2280
|
+
}
|
|
2281
|
+
function isTerminalCandidate(by, url) {
|
|
2282
|
+
return (by === 'fallback' || by === 'store') && isWebURL(url);
|
|
2283
|
+
}
|
|
2284
|
+
function notifyAttempt(onAttempt, attempt) {
|
|
2285
|
+
if (typeof onAttempt !== 'function')
|
|
2286
|
+
return;
|
|
2287
|
+
try {
|
|
2288
|
+
onAttempt(attempt);
|
|
2289
|
+
}
|
|
2290
|
+
catch (_) {
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
function describeAttempts(attempted) {
|
|
2294
|
+
var routes = [];
|
|
2295
|
+
for (var i = 0; i < attempted.length; i++)
|
|
2296
|
+
routes.push(attempted[i].by);
|
|
2297
|
+
return ' ' + attempted.length + ' candidate(s) attempted (' + joining(routes, undefined, ' \u2b62 ') + '). Read error.attempted for the URLs.';
|
|
2298
|
+
}
|
|
2299
|
+
function runCandidates(candidates, timeout, onAttempt, failure) {
|
|
2300
|
+
return new Promise(function (resolve, reject) {
|
|
2301
|
+
var attempted = [];
|
|
2302
|
+
var total = candidates.length;
|
|
2303
|
+
function step(index) {
|
|
2304
|
+
if (index >= total)
|
|
2305
|
+
return reject(createLaunchError('all-failed', failure + describeAttempts(attempted), attempted));
|
|
2306
|
+
var by = candidates[index][0];
|
|
2307
|
+
var url = candidates[index][1];
|
|
2308
|
+
var attempt = {
|
|
2309
|
+
by: by,
|
|
2310
|
+
url: typeof url === 'string' ? url : FUNCTION_FALLBACK_LABEL,
|
|
2311
|
+
index: index,
|
|
2312
|
+
total: total,
|
|
2313
|
+
};
|
|
2314
|
+
attempted.push(attempt);
|
|
2315
|
+
notifyAttempt(onAttempt, attempt);
|
|
2316
|
+
if (typeof url !== 'string') {
|
|
2317
|
+
try {
|
|
2318
|
+
url();
|
|
2319
|
+
}
|
|
2320
|
+
catch (_) {
|
|
2321
|
+
return step(index + 1);
|
|
2322
|
+
}
|
|
2323
|
+
return resolve(by);
|
|
2324
|
+
}
|
|
2325
|
+
openURL(index, url, timeout, isTerminalCandidate(by, url))
|
|
2326
|
+
.then(function () {
|
|
2327
|
+
resolve(by);
|
|
2328
|
+
})
|
|
2329
|
+
.catch(function () {
|
|
2330
|
+
step(index + 1);
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
step(0);
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2254
2336
|
function resolveOptions(option) {
|
|
2255
2337
|
var resolved = [];
|
|
2256
2338
|
var os = PlatformKit.os.name;
|
|
2257
2339
|
if (os === 'unknown')
|
|
2258
|
-
throw
|
|
2340
|
+
throw createLaunchError('unsupported-os', 'Cannot resolve app open options: unsupported or undetected OS. (userAgent: "' + PlatformKit.userAgent + '")', []);
|
|
2259
2341
|
var scheme;
|
|
2260
2342
|
var fallback;
|
|
2261
2343
|
var allowAppStore;
|
|
@@ -2265,6 +2347,7 @@ function resolveOptions(option) {
|
|
|
2265
2347
|
var timeout;
|
|
2266
2348
|
var intent;
|
|
2267
2349
|
var packageName;
|
|
2350
|
+
var allowIntent = true;
|
|
2268
2351
|
var bundleId;
|
|
2269
2352
|
var trackId;
|
|
2270
2353
|
var universal;
|
|
@@ -2274,6 +2357,7 @@ function resolveOptions(option) {
|
|
|
2274
2357
|
case 'android':
|
|
2275
2358
|
if (typeof option.android === 'undefined')
|
|
2276
2359
|
return [[], 0];
|
|
2360
|
+
allowIntent = option.android.allowIntent !== false;
|
|
2277
2361
|
intent = stripURL(option.android.intent);
|
|
2278
2362
|
scheme = stripURL(option.android.scheme);
|
|
2279
2363
|
fallback = stripURL(option.android.fallback);
|
|
@@ -2292,11 +2376,11 @@ function resolveOptions(option) {
|
|
|
2292
2376
|
if (typeof parsed.fallback !== 'undefined' && typeof fallback === 'undefined')
|
|
2293
2377
|
fallback = parsed.fallback;
|
|
2294
2378
|
}
|
|
2295
|
-
if (typeof scheme !== 'undefined' && typeof intent === 'undefined')
|
|
2379
|
+
if (allowIntent && typeof scheme !== 'undefined' && typeof intent === 'undefined')
|
|
2296
2380
|
intent = createIntentURL(scheme, packageName, fallback);
|
|
2297
2381
|
if (typeof timeout === 'undefined')
|
|
2298
2382
|
timeout = ANDROID_DEFAULT_TIMEOUT;
|
|
2299
|
-
if (typeof intent !== 'undefined' && canOpenIntent())
|
|
2383
|
+
if (allowIntent && typeof intent !== 'undefined' && canOpenIntent())
|
|
2300
2384
|
resolved.push(['intent', intent]);
|
|
2301
2385
|
if (typeof scheme !== 'undefined' && allowScheme)
|
|
2302
2386
|
resolved.push(['scheme', scheme]);
|
|
@@ -2685,7 +2769,7 @@ function resolveFile(module) {
|
|
|
2685
2769
|
done(true);
|
|
2686
2770
|
else
|
|
2687
2771
|
done(false);
|
|
2688
|
-
},
|
|
2772
|
+
}, CANCEL_DELAY);
|
|
2689
2773
|
}
|
|
2690
2774
|
function onvisibilitychange() {
|
|
2691
2775
|
if (!isDocumentHidden())
|
|
@@ -2817,6 +2901,8 @@ var LaunchKit = {
|
|
|
2817
2901
|
},
|
|
2818
2902
|
app: function (options) {
|
|
2819
2903
|
if (options === void 0) { options = {}; }
|
|
2904
|
+
if (APP_IN_FLIGHT !== null)
|
|
2905
|
+
return APP_IN_FLIGHT;
|
|
2820
2906
|
var resolved;
|
|
2821
2907
|
try {
|
|
2822
2908
|
resolved = resolveOptions(options);
|
|
@@ -2827,39 +2913,15 @@ var LaunchKit = {
|
|
|
2827
2913
|
var urls = resolved[0];
|
|
2828
2914
|
var timeout = resolved[1];
|
|
2829
2915
|
if (urls.length === 0)
|
|
2830
|
-
return Promise.reject(
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
var url = entry[1];
|
|
2840
|
-
if (typeof url === 'string') {
|
|
2841
|
-
tried[index] = url;
|
|
2842
|
-
return openURL(index, url, timeout)
|
|
2843
|
-
.then(function () {
|
|
2844
|
-
resolve(by);
|
|
2845
|
-
})
|
|
2846
|
-
.catch(function () {
|
|
2847
|
-
openURLSequential(index + 1);
|
|
2848
|
-
});
|
|
2849
|
-
}
|
|
2850
|
-
else {
|
|
2851
|
-
tried[index] = '[function fallback]';
|
|
2852
|
-
try {
|
|
2853
|
-
url();
|
|
2854
|
-
}
|
|
2855
|
-
catch (_) {
|
|
2856
|
-
return openURLSequential(index + 1);
|
|
2857
|
-
}
|
|
2858
|
-
resolve(by);
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
return openURLSequential();
|
|
2862
|
-
});
|
|
2916
|
+
return Promise.reject(createLaunchError('no-candidates', 'No openable URL candidates were resolved for the current OS ("' + PlatformKit.os.name + '"). Provide at least one of: scheme, intent, universal, fallback, or store id for this platform.', []));
|
|
2917
|
+
var attempt = runCandidates(urls, timeout, options.onAttempt, 'Failed to open the application.');
|
|
2918
|
+
function release() {
|
|
2919
|
+
if (APP_IN_FLIGHT === attempt)
|
|
2920
|
+
APP_IN_FLIGHT = null;
|
|
2921
|
+
}
|
|
2922
|
+
APP_IN_FLIGHT = attempt;
|
|
2923
|
+
attempt.then(release, release);
|
|
2924
|
+
return attempt;
|
|
2863
2925
|
},
|
|
2864
2926
|
telephone: function (options) {
|
|
2865
2927
|
if (options === void 0) { options = {}; }
|
|
@@ -2909,7 +2971,7 @@ var LaunchKit = {
|
|
|
2909
2971
|
map: function (options) {
|
|
2910
2972
|
if (options === void 0) { options = {}; }
|
|
2911
2973
|
var params = [];
|
|
2912
|
-
var
|
|
2974
|
+
var candidates = [];
|
|
2913
2975
|
var url = '';
|
|
2914
2976
|
switch (PlatformKit.os.name) {
|
|
2915
2977
|
case 'android':
|
|
@@ -2927,7 +2989,7 @@ var LaunchKit = {
|
|
|
2927
2989
|
params.push('z=' + options.zoom);
|
|
2928
2990
|
if (params.length > 0)
|
|
2929
2991
|
url = url + '?' + joining(params, undefined, '&');
|
|
2930
|
-
|
|
2992
|
+
candidates.push(['scheme', url]);
|
|
2931
2993
|
break;
|
|
2932
2994
|
case 'ios':
|
|
2933
2995
|
case 'macos':
|
|
@@ -2947,7 +3009,7 @@ var LaunchKit = {
|
|
|
2947
3009
|
}
|
|
2948
3010
|
if (typeof options.zoom !== 'undefined')
|
|
2949
3011
|
params.push('z=' + options.zoom);
|
|
2950
|
-
|
|
3012
|
+
candidates.push(['scheme', 'maps://?' + joining(params, undefined, '&')]);
|
|
2951
3013
|
break;
|
|
2952
3014
|
case 'windows':
|
|
2953
3015
|
if (typeof options.directions !== 'undefined') {
|
|
@@ -2963,10 +3025,10 @@ var LaunchKit = {
|
|
|
2963
3025
|
}
|
|
2964
3026
|
if (typeof options.zoom !== 'undefined')
|
|
2965
3027
|
params.push('lvl=' + options.zoom);
|
|
2966
|
-
|
|
3028
|
+
candidates.push(['scheme', 'bingmaps:?' + joining(params, undefined, '&')]);
|
|
2967
3029
|
}
|
|
2968
3030
|
if (typeof options.fallback !== 'undefined') {
|
|
2969
|
-
|
|
3031
|
+
candidates.push(['fallback', stripURL(options.fallback)]);
|
|
2970
3032
|
}
|
|
2971
3033
|
else {
|
|
2972
3034
|
var params_1 = ['api=1'];
|
|
@@ -2974,7 +3036,7 @@ var LaunchKit = {
|
|
|
2974
3036
|
params_1.push('destination=' + encodeMapValue(options.directions.destination));
|
|
2975
3037
|
if (typeof options.directions.origin !== 'undefined')
|
|
2976
3038
|
params_1.push('origin=' + encodeMapValue(options.directions.origin));
|
|
2977
|
-
|
|
3039
|
+
candidates.push(['fallback', 'https://www.google.com/maps/dir/?' + joining(params_1, undefined, '&')]);
|
|
2978
3040
|
}
|
|
2979
3041
|
else {
|
|
2980
3042
|
if (typeof options.coordinate !== 'undefined')
|
|
@@ -2983,34 +3045,10 @@ var LaunchKit = {
|
|
|
2983
3045
|
params_1.push('query=' + escapeURIComponentString(options.query));
|
|
2984
3046
|
if (typeof options.zoom !== 'undefined')
|
|
2985
3047
|
params_1.push('zoom=' + options.zoom);
|
|
2986
|
-
|
|
3048
|
+
candidates.push(['fallback', 'https://www.google.com/maps/search/?' + joining(params_1, undefined, '&')]);
|
|
2987
3049
|
}
|
|
2988
3050
|
}
|
|
2989
|
-
return
|
|
2990
|
-
var tried = [];
|
|
2991
|
-
function openURLSequential(index) {
|
|
2992
|
-
if (index === void 0) { index = 0; }
|
|
2993
|
-
if (index >= urls.length)
|
|
2994
|
-
return reject(new Error('Failed to open a map using all available URLs.\n\nAttempted URLs:\n' + joining(tried, undefined, '\n↓\n')));
|
|
2995
|
-
var url = urls[index];
|
|
2996
|
-
if (typeof url === 'string') {
|
|
2997
|
-
tried[index] = url;
|
|
2998
|
-
return openURL(index, url, getDefaultTimeout())
|
|
2999
|
-
.then(function () {
|
|
3000
|
-
resolve();
|
|
3001
|
-
})
|
|
3002
|
-
.catch(function () {
|
|
3003
|
-
openURLSequential(index + 1);
|
|
3004
|
-
});
|
|
3005
|
-
}
|
|
3006
|
-
else {
|
|
3007
|
-
tried[index] = '[function fallback]';
|
|
3008
|
-
url();
|
|
3009
|
-
resolve();
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
return openURLSequential();
|
|
3013
|
-
});
|
|
3051
|
+
return runCandidates(candidates, getDefaultTimeout(), options.onAttempt, 'Failed to open a map.');
|
|
3014
3052
|
},
|
|
3015
3053
|
filepicker: function (options) {
|
|
3016
3054
|
if (options === void 0) { options = {}; }
|