web-launch-kit 0.0.4 → 0.0.6
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 +53 -4
- package/README.md +54 -4
- package/dist/index.d.ts +17 -2
- package/dist/launch-kit.cjs +116 -81
- 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 +116 -81
- package/dist/launch-kit.mjs.map +1 -1
- package/dist/launch-kit.umd.js +116 -81
- 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 +2 -2
package/README.ko.md
CHANGED
|
@@ -24,18 +24,26 @@ npm install web-launch-kit
|
|
|
24
24
|
| 멤버 | 시그니처 | 설명 |
|
|
25
25
|
| --- | --- | --- |
|
|
26
26
|
| `LaunchKit.version` | `string` | 설치된 패키지 버전 |
|
|
27
|
+
| `LaunchKit.debug` | `boolean` (get/set) | 각 실행 시도 전에 `debugger`로 정지. 기본값 `false` |
|
|
27
28
|
| `LaunchKit.SettingType` | `enum` | 설정 화면: `General`, `Network`, `Display`, `Appearance`, `Accessibility`, `Battery`, `Datetime`, `Language`, `Accounts`, `Storage` |
|
|
28
29
|
| `LaunchKit.app(options?)` | `Promise<AppOpenedBy>` | 가능한 최선의 경로로 앱을 실행하고, 어떤 경로로 열렸는지 반환 |
|
|
29
30
|
| `LaunchKit.telephone(options?)` | `Promise<void>` | 전화 앱 열기 (`tel:`) |
|
|
30
31
|
| `LaunchKit.message(options?)` | `Promise<void>` | 문자 작성 열기 (`sms:`) |
|
|
31
32
|
| `LaunchKit.mail(options?)` | `Promise<void>` | 메일 작성 열기 (`mailto:`) |
|
|
32
|
-
| `LaunchKit.map(options?)` | `Promise<
|
|
33
|
+
| `LaunchKit.map(options?)` | `Promise<AppOpenedBy>` | 검색어·좌표·경로로 지도 열기. OS별 네이티브 앱과 Google Maps 웹 폴백 |
|
|
33
34
|
| `LaunchKit.filepicker(options?)` | `Promise<File[]>` | 파일 또는 디렉터리 선택 (File System Access API, input 폴백) |
|
|
34
35
|
| `LaunchKit.setting(type?)` | `Promise<void>` | 지원되는 환경에서 시스템 설정 화면 열기 |
|
|
35
36
|
| `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getter와 비동기 `getTrackId` / `getProductId` |
|
|
36
37
|
|
|
37
38
|
`AppOpenedBy`는 `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"` 중 하나입니다.
|
|
38
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
|
+
|
|
39
47
|
---
|
|
40
48
|
|
|
41
49
|
## ESM
|
|
@@ -199,6 +207,21 @@ console.log(openedBy) // "universal" | "scheme" | "intent" | "fallback" | "store
|
|
|
199
207
|
`scheme` / `packageFamilyName` / `productId`, macOS는 `scheme` / `bundleId` / `trackId`를
|
|
200
208
|
받습니다. 모두 `fallback`, `timeout`, `allowAppStore`, `allowWebStore`를 함께 받습니다.
|
|
201
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
|
+
|
|
202
225
|
안드로이드와 iOS는 `assumeAllowedInApp`을 추가로 받습니다. 파트너 화이트리스트로 동작하는
|
|
203
226
|
인앱 브라우저(예: 웨이보)에 앱이 등록되어 있을 때만 `true`로 선언하세요. 그러면 그 환경에서
|
|
204
227
|
scheme·유니버설 링크 후보가 유지됩니다. 완전히 차단된 웹뷰나 OS 버전 요구사항은 이 옵션으로
|
|
@@ -233,6 +256,9 @@ const files = await LaunchKit.filepicker({ accept: ['image/*', '.pdf'], multiple
|
|
|
233
256
|
const tree = await LaunchKit.filepicker({ directory: true })
|
|
234
257
|
```
|
|
235
258
|
|
|
259
|
+
취소하면 `[]`로 resolve합니다. `cancel` 이벤트가 있는 엔진은 즉시 알려주고, 나머지는 값이 빈
|
|
260
|
+
상태로 포커스가 돌아오는 것으로 판별하므로 조금 더 걸립니다.
|
|
261
|
+
|
|
236
262
|
## 지도
|
|
237
263
|
|
|
238
264
|
`map()`은 검색어·좌표·경로 중 하나를 받아 현재 OS에 맞는 URL을 만듭니다
|
|
@@ -259,6 +285,24 @@ await LaunchKit.map({ directions: { destination: 'Seoul Station', origin: [37.56
|
|
|
259
285
|
경로는 Google Maps 폴백을 쓰고, Windows도 지도 앱이 단종되어 `bingmaps:`가 대개 웹 폴백으로
|
|
260
286
|
넘어갑니다.
|
|
261
287
|
|
|
288
|
+
## 콘솔에서 테스트하기
|
|
289
|
+
|
|
290
|
+
DevTools 콘솔에서 실행 메서드를 호출하면 페이지에 포커스도, 사용자 activation도 없는
|
|
291
|
+
상태가 됩니다. 둘 다 프로그램으로는 되돌릴 수 없어서 시도가 실패하고 앱 전환 감지도 볼 것이
|
|
292
|
+
없습니다. 같은 호출을 클릭 핸들러 안에서 하면 정상 동작합니다.
|
|
293
|
+
|
|
294
|
+
`debug`를 켜면 화면 이동 직전에 정지합니다. 재개 버튼을 누르면 포커스와 activation이
|
|
295
|
+
돌아와서 콘솔에서도 실제 탭과 동일하게 동작합니다.
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
LaunchKit.debug = true
|
|
299
|
+
|
|
300
|
+
await LaunchKit.app({ ios: { scheme: 'myapp://profile/42' } })
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
그 외에는 꺼두세요. 모든 실행 메서드가 같은 지점을 지나므로 `telephone`, `message`,
|
|
304
|
+
`mail`, `map`에서도 후보마다 한 번씩 정지합니다.
|
|
305
|
+
|
|
262
306
|
## 시스템 설정
|
|
263
307
|
|
|
264
308
|
```js
|
|
@@ -279,15 +323,20 @@ if (LaunchKit.utils.canOpenSetting) {
|
|
|
279
323
|
- **`app()`이 반환하는 것은 경로이지 실행 보증이 아닙니다.** 감지는 OS별 타임아웃을 둔
|
|
280
324
|
포커스·가시성 휴리스틱에 기반합니다. `AppOpenedBy`가 반환됐다는 건 그 후보가 시도되었고
|
|
281
325
|
페이지가 백그라운드로 전환된 것처럼 보였다는 뜻이지, 확정된 실행 확인은 아닙니다.
|
|
282
|
-
- **`utils.getTrackId` / `getProductId`는 비동기**이고
|
|
283
|
-
|
|
326
|
+
- **`utils.getTrackId` / `getProductId`는 비동기**이고 1시간 캐시됩니다. 페이지 초기화 때
|
|
327
|
+
한 번 호출해두면 `app()`이 id를 인라인으로 조회하지 않습니다. `app()`은 사용자 제스처 안에서
|
|
328
|
+
실행돼야 하므로, id가 없으면 `await`으로 user activation을 잃는 대신 동기로 조회합니다.
|
|
329
|
+
`trackId` / `productId`를 직접 주면 조회 자체가 생략됩니다.
|
|
284
330
|
- **스토어 id 조회는 외부 API에 의존합니다**(iTunes Lookup, Microsoft display catalog).
|
|
285
331
|
1시간 캐시되며 실패는 예외가 아니라 `undefined`로 resolve됩니다.
|
|
286
332
|
- **인앱 브라우저는 후보 단위로 걸러집니다.** 실행이 차단된 웹뷰(WeChat, QQ, Qzone, Baidu,
|
|
287
333
|
그리고 파트너가 아닌 웨이보) 안에서는 커스텀 스킴·`intent://`·`market://` 같은 스토어 스킴이
|
|
288
334
|
타임아웃을 소진하지 않고 미리 제외됩니다. 그래서 체인이 곧바로 `fallback` / 웹스토어로
|
|
289
335
|
떨어집니다. 화이트리스트에 등록된 파트너 앱은 `assumeAllowedInApp: true`로 되돌립니다.
|
|
290
|
-
- **`map()`은 각 후보에 OS별 타임아웃**을
|
|
336
|
+
- **`map()`은 각 후보에 OS별 타임아웃**을 사용하고, `app()`처럼 성공한 경로를 반환합니다.
|
|
337
|
+
- **웹 폴백은 내비게이션 시점에 resolve합니다.** `fallback`이나 웹스토어 후보는 문서 자체를
|
|
338
|
+
교체하므로 기다릴 앱 전환이 없습니다. 이동을 시작하는 즉시 resolve하며, 이동이 성공한 뒤에
|
|
339
|
+
reject되는 일이 없습니다.
|
|
291
340
|
|
|
292
341
|
## 브라우저 지원
|
|
293
342
|
|
package/README.md
CHANGED
|
@@ -25,18 +25,26 @@ The bundle is self-contained (OS/locale detection is inlined) — no peer script
|
|
|
25
25
|
| Member | Signature | Description |
|
|
26
26
|
| --- | --- | --- |
|
|
27
27
|
| `LaunchKit.version` | `string` | The installed package version |
|
|
28
|
+
| `LaunchKit.debug` | `boolean` (get/set) | Pause on a `debugger` before each launch attempt; defaults to `false` |
|
|
28
29
|
| `LaunchKit.SettingType` | `enum` | Setting panes: `General`, `Network`, `Display`, `Appearance`, `Accessibility`, `Battery`, `Datetime`, `Language`, `Accounts`, `Storage` |
|
|
29
30
|
| `LaunchKit.app(options?)` | `Promise<AppOpenedBy>` | Launch an app via the best available route; resolves with which route opened it |
|
|
30
31
|
| `LaunchKit.telephone(options?)` | `Promise<void>` | Open the dialer (`tel:`) |
|
|
31
32
|
| `LaunchKit.message(options?)` | `Promise<void>` | Open the SMS composer (`sms:`) |
|
|
32
33
|
| `LaunchKit.mail(options?)` | `Promise<void>` | Open the mail composer (`mailto:`) |
|
|
33
|
-
| `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 |
|
|
34
35
|
| `LaunchKit.filepicker(options?)` | `Promise<File[]>` | Pick files or a directory (File System Access API, with input fallback) |
|
|
35
36
|
| `LaunchKit.setting(type?)` | `Promise<void>` | Open a system-settings pane where supported |
|
|
36
37
|
| `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getters, plus async `getTrackId` / `getProductId` |
|
|
37
38
|
|
|
38
39
|
`AppOpenedBy` is one of: `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"`.
|
|
39
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
|
+
|
|
40
48
|
---
|
|
41
49
|
|
|
42
50
|
## ESM
|
|
@@ -201,6 +209,21 @@ Per-platform fields: Android accepts `intent` / `scheme` / `packageName` / `fall
|
|
|
201
209
|
macOS accepts `scheme` / `bundleId` / `trackId`. All accept `fallback`, `timeout`,
|
|
202
210
|
`allowAppStore`, `allowWebStore`.
|
|
203
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' ? '스토어로 이동합니다' : '앱을 여는 중… (' + (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
|
+
|
|
204
227
|
Android and iOS additionally accept
|
|
205
228
|
`assumeAllowedInApp` — declare it `true` only when your app is whitelisted by a
|
|
206
229
|
partner-gated in-app browser (e.g. Weibo) so scheme / universal-link candidates are
|
|
@@ -235,6 +258,9 @@ const files = await LaunchKit.filepicker({ accept: ['image/*', '.pdf'], multiple
|
|
|
235
258
|
const tree = await LaunchKit.filepicker({ directory: true })
|
|
236
259
|
```
|
|
237
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
|
+
|
|
238
264
|
## Maps
|
|
239
265
|
|
|
240
266
|
`map()` takes an intent — a search query, a coordinate, or a route — and builds the
|
|
@@ -262,6 +288,24 @@ handlers other than Google Maps misread the labelled form as a search. Android's
|
|
|
262
288
|
fallback there, and on Windows the discontinued Maps app means `bingmaps:` usually
|
|
263
289
|
defers to the web fallback too.
|
|
264
290
|
|
|
291
|
+
## Testing from the console
|
|
292
|
+
|
|
293
|
+
Calling a launch method from the DevTools console leaves the page without focus and without
|
|
294
|
+
user activation. Neither can be restored programmatically, so the attempt fails and
|
|
295
|
+
app-switch detection has nothing to observe — the same call from a click handler works fine.
|
|
296
|
+
|
|
297
|
+
Set `debug` to pause before the navigation. Pressing resume hands focus and activation back,
|
|
298
|
+
so the console behaves like a real tap.
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
LaunchKit.debug = true
|
|
302
|
+
|
|
303
|
+
await LaunchKit.app({ ios: { scheme: 'myapp://profile/42' } })
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Leave it off otherwise — every launch method routes through the same place, so it would
|
|
307
|
+
pause on `telephone`, `message`, `mail`, and `map` too, once per candidate.
|
|
308
|
+
|
|
265
309
|
## System settings
|
|
266
310
|
|
|
267
311
|
```js
|
|
@@ -282,8 +326,10 @@ if (LaunchKit.utils.canOpenSetting) {
|
|
|
282
326
|
- **`app()` resolves with the route, not a guarantee of launch.** Detection relies on
|
|
283
327
|
focus/visibility heuristics with per-OS timeouts; a resolved `AppOpenedBy` means that
|
|
284
328
|
candidate was attempted and the page appeared to background, not a hard confirmation.
|
|
285
|
-
- **`utils.getTrackId` / `getProductId` are async** and
|
|
286
|
-
|
|
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.
|
|
287
333
|
- **Store-id lookups depend on remote APIs** (iTunes Lookup, Microsoft display catalog)
|
|
288
334
|
and are cached for one hour; failures resolve to `undefined` rather than throwing.
|
|
289
335
|
- **In-app browsers are gated per candidate.** Inside webviews that block launches
|
|
@@ -292,7 +338,11 @@ if (LaunchKit.utils.canOpenSetting) {
|
|
|
292
338
|
burning their timeouts, so the chain falls straight through to `fallback` / web store
|
|
293
339
|
(the only routes that work there). Whitelisted partner apps opt back in with
|
|
294
340
|
`assumeAllowedInApp: true`.
|
|
295
|
-
- **`map()` uses the per-OS timeout** for each candidate
|
|
341
|
+
- **`map()` uses the per-OS timeout** for each candidate, and resolves with the route that
|
|
342
|
+
worked just like `app()`.
|
|
343
|
+
- **Web fallbacks resolve on navigation.** A `fallback` or web-store candidate replaces the
|
|
344
|
+
document, so there is no app switch to wait for — those resolve as soon as the navigation
|
|
345
|
+
is issued instead of risking a rejection after the move already succeeded.
|
|
296
346
|
|
|
297
347
|
## Browser support
|
|
298
348
|
|
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",
|
|
@@ -46,6 +57,7 @@ declare interface AppOpenOptions {
|
|
|
46
57
|
ios?: IOSAppInfo;
|
|
47
58
|
windows?: WindowsAppInfo;
|
|
48
59
|
macos?: MacOSAppInfo;
|
|
60
|
+
onAttempt?: (attempt: LaunchAttempt) => void;
|
|
49
61
|
}
|
|
50
62
|
declare interface TelephoneOptions {
|
|
51
63
|
to?: string;
|
|
@@ -78,6 +90,7 @@ declare interface MapOptions {
|
|
|
78
90
|
};
|
|
79
91
|
zoom?: number;
|
|
80
92
|
fallback?: URLStringOrFallback;
|
|
93
|
+
onAttempt?: (attempt: LaunchAttempt) => void;
|
|
81
94
|
}
|
|
82
95
|
interface LaunchKitUtils {
|
|
83
96
|
get canOpenIntent(): boolean;
|
|
@@ -89,16 +102,18 @@ interface LaunchKitUtils {
|
|
|
89
102
|
interface LaunchKitInstance {
|
|
90
103
|
readonly version: string;
|
|
91
104
|
readonly SettingType: typeof SettingType;
|
|
105
|
+
get debug(): boolean;
|
|
106
|
+
set debug(value: boolean);
|
|
92
107
|
readonly utils: LaunchKitUtils;
|
|
93
108
|
app(options?: AppOpenOptions): Promise<AppOpenedBy>;
|
|
94
109
|
telephone(options?: TelephoneOptions): Promise<void>;
|
|
95
110
|
message(options?: MessageOptions): Promise<void>;
|
|
96
111
|
mail(options?: MailOptions): Promise<void>;
|
|
97
|
-
map(options?: MapOptions): Promise<
|
|
112
|
+
map(options?: MapOptions): Promise<AppOpenedBy>;
|
|
98
113
|
filepicker(options?: FilepickerOptions): Promise<File[]>;
|
|
99
114
|
setting(type?: SettingType): Promise<void>;
|
|
100
115
|
}
|
|
101
116
|
declare const LaunchKit: LaunchKitInstance;
|
|
102
117
|
|
|
103
118
|
export { SettingType, LaunchKit as default };
|
|
104
|
-
export type { AndroidAppInfo, AppInfo, AppOpenOptions, AppOpenedBy, FilepickerOptions, IOSAppInfo, LaunchKitInstance, MacOSAppInfo, MailOptions, MapOptions, MessageOptions, OpenPickerStartIn, TelephoneOptions,
|
|
119
|
+
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.6";
|
|
6
6
|
var packageJSON$3 = {
|
|
7
7
|
version: version$3};
|
|
8
8
|
|
|
@@ -262,7 +262,7 @@ function parseWebview() {
|
|
|
262
262
|
return true;
|
|
263
263
|
if (/\belectron\//i.test(currentUserAgent))
|
|
264
264
|
return true;
|
|
265
|
-
if (/
|
|
265
|
+
if (/iphone|ipad|ipod/i.test(currentUserAgent) && /applewebkit/i.test(currentUserAgent) && /safari/i.test(currentUserAgent) === false)
|
|
266
266
|
return true;
|
|
267
267
|
return parseInAppBrowser() !== null;
|
|
268
268
|
}
|
|
@@ -492,7 +492,7 @@ function getGlobal$2() {
|
|
|
492
492
|
return {};
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
var version$1 = "0.0.
|
|
495
|
+
var version$1 = "0.0.3";
|
|
496
496
|
var packageJSON$1 = {
|
|
497
497
|
version: version$1};
|
|
498
498
|
|
|
@@ -942,8 +942,6 @@ var GLOBAL$4 = getGlobal$2();
|
|
|
942
942
|
var LISTENER_STORE = [];
|
|
943
943
|
var NOOP = function () {
|
|
944
944
|
};
|
|
945
|
-
// Records live on the target itself, so they are collected with it and lookups only scan
|
|
946
|
-
// that target's own listeners. Targets that reject the expando fall back to LISTENER_STORE.
|
|
947
945
|
var STORE_KEY = '__webEventKitListeners__';
|
|
948
946
|
function resolveBucket(target, create) {
|
|
949
947
|
var holder = target;
|
|
@@ -1001,9 +999,6 @@ function invokeCallback(callback, target, event) {
|
|
|
1001
999
|
if (typeof callback === 'object' && callback !== null && typeof callback.handleEvent === 'function')
|
|
1002
1000
|
return callback.handleEvent(event);
|
|
1003
1001
|
}
|
|
1004
|
-
// The same Event object is handed to every listener, so any property this kit overrides has
|
|
1005
|
-
// to be put back before the next one runs — otherwise a `pointerdown` listener would observe
|
|
1006
|
-
// the `touchstart` type and synthetic touches written by a listener registered before it.
|
|
1007
1002
|
function patchEventProperty(event, property, value, patches) {
|
|
1008
1003
|
var holder = event;
|
|
1009
1004
|
var had = false;
|
|
@@ -1169,8 +1164,6 @@ var EventKit = {
|
|
|
1169
1164
|
return NOOP;
|
|
1170
1165
|
var normalized = normalizeListenerOptions(options);
|
|
1171
1166
|
var existing = findListenerRecord(target, type, callback, normalized.capture);
|
|
1172
|
-
// Native `addEventListener` ignores a duplicate registration, so hand back a releaser
|
|
1173
|
-
// that does nothing rather than one that would detach the first caller's listener.
|
|
1174
1167
|
if (typeof existing !== 'undefined')
|
|
1175
1168
|
return NOOP;
|
|
1176
1169
|
if (typeof normalized.signal !== 'undefined' && normalized.signal.aborted)
|
|
@@ -1200,8 +1193,6 @@ var EventKit = {
|
|
|
1200
1193
|
record.onAbort = function () {
|
|
1201
1194
|
releaseListenerRecord(record);
|
|
1202
1195
|
};
|
|
1203
|
-
// Engines without listener-options coerce `{once: true}` to capture, which would then
|
|
1204
|
-
// fail to match on removal; releaseListenerRecord detaches this anyway.
|
|
1205
1196
|
record.signal.addEventListener('abort', record.onAbort, false);
|
|
1206
1197
|
}
|
|
1207
1198
|
return function () {
|
|
@@ -1906,6 +1897,8 @@ var DEFAULT_TIMEOUT = 750;
|
|
|
1906
1897
|
var GLOBAL = getGlobal$1();
|
|
1907
1898
|
var NAVIGATOR = GLOBAL.navigator;
|
|
1908
1899
|
var CLEANUP_INPUT_ELEMENT = null;
|
|
1900
|
+
var DEBUG = false;
|
|
1901
|
+
var APP_IN_FLIGHT = null;
|
|
1909
1902
|
function createTypeGuard(tag) {
|
|
1910
1903
|
return function (value) {
|
|
1911
1904
|
return Object.prototype.toString.call(value) === '[object ' + tag + ']';
|
|
@@ -2122,7 +2115,8 @@ function resolveFocusEventConfig() {
|
|
|
2122
2115
|
};
|
|
2123
2116
|
}
|
|
2124
2117
|
}
|
|
2125
|
-
function openURL(tried, url, timeout) {
|
|
2118
|
+
function openURL(tried, url, timeout, terminal) {
|
|
2119
|
+
if (terminal === void 0) { terminal = false; }
|
|
2126
2120
|
var config = resolveFocusEventConfig();
|
|
2127
2121
|
var top = getTopmostWindow();
|
|
2128
2122
|
var topDocument = top.document;
|
|
@@ -2160,6 +2154,8 @@ function openURL(tried, url, timeout) {
|
|
|
2160
2154
|
}
|
|
2161
2155
|
}
|
|
2162
2156
|
}
|
|
2157
|
+
if (terminal)
|
|
2158
|
+
return;
|
|
2163
2159
|
try {
|
|
2164
2160
|
iframe = createHiddenElement('iframe');
|
|
2165
2161
|
iframe.src = url;
|
|
@@ -2178,8 +2174,20 @@ function openURL(tried, url, timeout) {
|
|
|
2178
2174
|
catch (_) {
|
|
2179
2175
|
}
|
|
2180
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
|
+
}
|
|
2181
2188
|
return new Promise(function (resolve, reject) {
|
|
2182
|
-
|
|
2189
|
+
if (DEBUG)
|
|
2190
|
+
debugger;
|
|
2183
2191
|
var timeoutId;
|
|
2184
2192
|
var resolved = false;
|
|
2185
2193
|
function cleanup() {
|
|
@@ -2258,11 +2266,78 @@ function openURL(tried, url, timeout) {
|
|
|
2258
2266
|
}
|
|
2259
2267
|
});
|
|
2260
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
|
+
}
|
|
2261
2336
|
function resolveOptions(option) {
|
|
2262
2337
|
var resolved = [];
|
|
2263
2338
|
var os = PlatformKit.os.name;
|
|
2264
2339
|
if (os === 'unknown')
|
|
2265
|
-
throw
|
|
2340
|
+
throw createLaunchError('unsupported-os', 'Cannot resolve app open options: unsupported or undetected OS. (userAgent: "' + PlatformKit.userAgent + '")', []);
|
|
2266
2341
|
var scheme;
|
|
2267
2342
|
var fallback;
|
|
2268
2343
|
var allowAppStore;
|
|
@@ -2692,7 +2767,7 @@ function resolveFile(module) {
|
|
|
2692
2767
|
done(true);
|
|
2693
2768
|
else
|
|
2694
2769
|
done(false);
|
|
2695
|
-
},
|
|
2770
|
+
}, CANCEL_DELAY);
|
|
2696
2771
|
}
|
|
2697
2772
|
function onvisibilitychange() {
|
|
2698
2773
|
if (!isDocumentHidden())
|
|
@@ -2803,6 +2878,12 @@ function walk(directory, basePath) {
|
|
|
2803
2878
|
var LaunchKit = {
|
|
2804
2879
|
SettingType: exports.SettingType,
|
|
2805
2880
|
version: packageJSON$3.version,
|
|
2881
|
+
get debug() {
|
|
2882
|
+
return DEBUG;
|
|
2883
|
+
},
|
|
2884
|
+
set debug(value) {
|
|
2885
|
+
DEBUG = value === true;
|
|
2886
|
+
},
|
|
2806
2887
|
utils: {
|
|
2807
2888
|
get canOpenIntent() {
|
|
2808
2889
|
return canOpenIntent();
|
|
@@ -2818,6 +2899,8 @@ var LaunchKit = {
|
|
|
2818
2899
|
},
|
|
2819
2900
|
app: function (options) {
|
|
2820
2901
|
if (options === void 0) { options = {}; }
|
|
2902
|
+
if (APP_IN_FLIGHT !== null)
|
|
2903
|
+
return APP_IN_FLIGHT;
|
|
2821
2904
|
var resolved;
|
|
2822
2905
|
try {
|
|
2823
2906
|
resolved = resolveOptions(options);
|
|
@@ -2828,39 +2911,15 @@ var LaunchKit = {
|
|
|
2828
2911
|
var urls = resolved[0];
|
|
2829
2912
|
var timeout = resolved[1];
|
|
2830
2913
|
if (urls.length === 0)
|
|
2831
|
-
return Promise.reject(
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
var url = entry[1];
|
|
2841
|
-
if (typeof url === 'string') {
|
|
2842
|
-
tried[index] = url;
|
|
2843
|
-
return openURL(index, url, timeout)
|
|
2844
|
-
.then(function () {
|
|
2845
|
-
resolve(by);
|
|
2846
|
-
})
|
|
2847
|
-
.catch(function () {
|
|
2848
|
-
openURLSequential(index + 1);
|
|
2849
|
-
});
|
|
2850
|
-
}
|
|
2851
|
-
else {
|
|
2852
|
-
tried[index] = '[function fallback]';
|
|
2853
|
-
try {
|
|
2854
|
-
url();
|
|
2855
|
-
}
|
|
2856
|
-
catch (_) {
|
|
2857
|
-
return openURLSequential(index + 1);
|
|
2858
|
-
}
|
|
2859
|
-
resolve(by);
|
|
2860
|
-
}
|
|
2861
|
-
}
|
|
2862
|
-
return openURLSequential();
|
|
2863
|
-
});
|
|
2914
|
+
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.', []));
|
|
2915
|
+
var attempt = runCandidates(urls, timeout, options.onAttempt, 'Failed to open the application.');
|
|
2916
|
+
function release() {
|
|
2917
|
+
if (APP_IN_FLIGHT === attempt)
|
|
2918
|
+
APP_IN_FLIGHT = null;
|
|
2919
|
+
}
|
|
2920
|
+
APP_IN_FLIGHT = attempt;
|
|
2921
|
+
attempt.then(release, release);
|
|
2922
|
+
return attempt;
|
|
2864
2923
|
},
|
|
2865
2924
|
telephone: function (options) {
|
|
2866
2925
|
if (options === void 0) { options = {}; }
|
|
@@ -2910,7 +2969,7 @@ var LaunchKit = {
|
|
|
2910
2969
|
map: function (options) {
|
|
2911
2970
|
if (options === void 0) { options = {}; }
|
|
2912
2971
|
var params = [];
|
|
2913
|
-
var
|
|
2972
|
+
var candidates = [];
|
|
2914
2973
|
var url = '';
|
|
2915
2974
|
switch (PlatformKit.os.name) {
|
|
2916
2975
|
case 'android':
|
|
@@ -2928,7 +2987,7 @@ var LaunchKit = {
|
|
|
2928
2987
|
params.push('z=' + options.zoom);
|
|
2929
2988
|
if (params.length > 0)
|
|
2930
2989
|
url = url + '?' + joining(params, undefined, '&');
|
|
2931
|
-
|
|
2990
|
+
candidates.push(['scheme', url]);
|
|
2932
2991
|
break;
|
|
2933
2992
|
case 'ios':
|
|
2934
2993
|
case 'macos':
|
|
@@ -2948,7 +3007,7 @@ var LaunchKit = {
|
|
|
2948
3007
|
}
|
|
2949
3008
|
if (typeof options.zoom !== 'undefined')
|
|
2950
3009
|
params.push('z=' + options.zoom);
|
|
2951
|
-
|
|
3010
|
+
candidates.push(['scheme', 'maps://?' + joining(params, undefined, '&')]);
|
|
2952
3011
|
break;
|
|
2953
3012
|
case 'windows':
|
|
2954
3013
|
if (typeof options.directions !== 'undefined') {
|
|
@@ -2964,10 +3023,10 @@ var LaunchKit = {
|
|
|
2964
3023
|
}
|
|
2965
3024
|
if (typeof options.zoom !== 'undefined')
|
|
2966
3025
|
params.push('lvl=' + options.zoom);
|
|
2967
|
-
|
|
3026
|
+
candidates.push(['scheme', 'bingmaps:?' + joining(params, undefined, '&')]);
|
|
2968
3027
|
}
|
|
2969
3028
|
if (typeof options.fallback !== 'undefined') {
|
|
2970
|
-
|
|
3029
|
+
candidates.push(['fallback', stripURL(options.fallback)]);
|
|
2971
3030
|
}
|
|
2972
3031
|
else {
|
|
2973
3032
|
var params_1 = ['api=1'];
|
|
@@ -2975,7 +3034,7 @@ var LaunchKit = {
|
|
|
2975
3034
|
params_1.push('destination=' + encodeMapValue(options.directions.destination));
|
|
2976
3035
|
if (typeof options.directions.origin !== 'undefined')
|
|
2977
3036
|
params_1.push('origin=' + encodeMapValue(options.directions.origin));
|
|
2978
|
-
|
|
3037
|
+
candidates.push(['fallback', 'https://www.google.com/maps/dir/?' + joining(params_1, undefined, '&')]);
|
|
2979
3038
|
}
|
|
2980
3039
|
else {
|
|
2981
3040
|
if (typeof options.coordinate !== 'undefined')
|
|
@@ -2984,34 +3043,10 @@ var LaunchKit = {
|
|
|
2984
3043
|
params_1.push('query=' + escapeURIComponentString(options.query));
|
|
2985
3044
|
if (typeof options.zoom !== 'undefined')
|
|
2986
3045
|
params_1.push('zoom=' + options.zoom);
|
|
2987
|
-
|
|
3046
|
+
candidates.push(['fallback', 'https://www.google.com/maps/search/?' + joining(params_1, undefined, '&')]);
|
|
2988
3047
|
}
|
|
2989
3048
|
}
|
|
2990
|
-
return
|
|
2991
|
-
var tried = [];
|
|
2992
|
-
function openURLSequential(index) {
|
|
2993
|
-
if (index === void 0) { index = 0; }
|
|
2994
|
-
if (index >= urls.length)
|
|
2995
|
-
return reject(new Error('Failed to open a map using all available URLs.\n\nAttempted URLs:\n' + joining(tried, undefined, '\n↓\n')));
|
|
2996
|
-
var url = urls[index];
|
|
2997
|
-
if (typeof url === 'string') {
|
|
2998
|
-
tried[index] = url;
|
|
2999
|
-
return openURL(index, url, getDefaultTimeout())
|
|
3000
|
-
.then(function () {
|
|
3001
|
-
resolve();
|
|
3002
|
-
})
|
|
3003
|
-
.catch(function () {
|
|
3004
|
-
openURLSequential(index + 1);
|
|
3005
|
-
});
|
|
3006
|
-
}
|
|
3007
|
-
else {
|
|
3008
|
-
tried[index] = '[function fallback]';
|
|
3009
|
-
url();
|
|
3010
|
-
resolve();
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
return openURLSequential();
|
|
3014
|
-
});
|
|
3049
|
+
return runCandidates(candidates, getDefaultTimeout(), options.onAttempt, 'Failed to open a map.');
|
|
3015
3050
|
},
|
|
3016
3051
|
filepicker: function (options) {
|
|
3017
3052
|
if (options === void 0) { options = {}; }
|