web-launch-kit 0.0.7 → 0.0.8

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 CHANGED
@@ -33,7 +33,8 @@ npm install web-launch-kit
33
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
- | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getter와 비동기 `getTrackId` / `getProductId` |
36
+ | `LaunchKit.escapeWebview(options?)` | `Promise<void>` | URL(기본값은 현재 페이지)을 외부 브라우저에서 다시 열어 인앱 웹뷰에서 탈출 |
37
+ | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` / `canEscapeWebview` getter와 비동기 `getTrackId` / `getProductId` |
37
38
 
38
39
  `AppOpenedBy`는 `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"` 중 하나입니다.
39
40
 
@@ -82,8 +83,8 @@ LaunchKit.telephone({ to: '+821012345678' })
82
83
 
83
84
  싱글톤의 형태는 `LaunchKitInstance`로 export되고, 모든 옵션 객체에 이름이 붙어 있습니다 —
84
85
  `AppOpenOptions`, `AndroidAppInfo`, `IOSAppInfo`, `WindowsAppInfo`, `MacOSAppInfo`,
85
- `TelephoneOptions`, `MessageOptions`, `MailOptions`, `MapOptions`, `FilepickerOptions`.
86
- `SettingType`은 값 export이고 `AppOpenedBy`는 타입입니다.
86
+ `TelephoneOptions`, `MessageOptions`, `MailOptions`, `MapOptions`, `FilepickerOptions`,
87
+ `EscapeWebviewOptions`. `SettingType`은 값 export이고 `AppOpenedBy`는 타입입니다.
87
88
 
88
89
  ```ts
89
90
  import LaunchKit, {
@@ -313,6 +314,32 @@ if (LaunchKit.utils.canOpenSetting) {
313
314
  }
314
315
  ```
315
316
 
317
+ ## 인앱 브라우저 탈출
318
+
319
+ `escapeWebview()`는 URL — 기본값은 현재 페이지 — 을 외부 기본 브라우저에서 다시 열어,
320
+ 페이지가 갇혀 있는 인앱 웹뷰에서 탈출합니다.
321
+
322
+ ```js
323
+ import LaunchKit from 'web-launch-kit'
324
+
325
+ if (LaunchKit.utils.canEscapeWebview) {
326
+ await LaunchKit.escapeWebview()
327
+ }
328
+
329
+ // 현재 페이지 대신 특정 URL을 내보낼 수도 있습니다
330
+ await LaunchKit.escapeWebview({ url: 'https://example.com/checkout' })
331
+ ```
332
+
333
+ 경로는 환경에 따라 결정됩니다. 카카오톡은 전용 `kakaotalk://web/openExternal` 스킴을
334
+ 제공하고, LINE은 공식 문서화된 `openExternalBrowser=1` 쿼리 파라미터를 지원합니다 —
335
+ 둘 다 안드로이드와 iOS에서 동작합니다. 그 외 웹뷰는 OS 경로로 폴백합니다. 안드로이드는
336
+ Chrome을 고정한 `intent://` URL(원본 URL이 `S.browser_fallback_url`로 실려서 Chrome이
337
+ 없는 기기는 제자리에 머뭅니다), iOS는 비공개 `x-safari-` 스킴입니다 — iOS 15와 17+에서
338
+ 동작하고 16에서는 동작하지 않으며, 기본 브라우저 설정과 무관하게 항상 Safari를 열고,
339
+ Meta 계열 웹뷰(Facebook, Instagram) 안에서는 차단됩니다. http(s) URL만 다시 열 수
340
+ 있습니다. 웹뷰 밖이거나 사용 가능한 경로가 없으면(완전 차단 웹뷰, iOS 16, 데스크톱)
341
+ 프로미스가 reject됩니다 — 버튼 노출은 `utils.canEscapeWebview`로 게이트하세요.
342
+
316
343
  ---
317
344
 
318
345
  ## 참고
package/README.md CHANGED
@@ -34,7 +34,8 @@ The bundle is self-contained (OS/locale detection is inlined) — no peer script
34
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
- | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` getters, plus async `getTrackId` / `getProductId` |
37
+ | `LaunchKit.escapeWebview(options?)` | `Promise<void>` | Reopen a URL (the current page by default) in the external browser, escaping an in-app webview |
38
+ | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` / `canEscapeWebview` getters, plus async `getTrackId` / `getProductId` |
38
39
 
39
40
  `AppOpenedBy` is one of: `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"`.
40
41
 
@@ -84,7 +85,8 @@ The global `LaunchKit` is a namespace object; the singleton is `LaunchKit.defaul
84
85
  The singleton shape is exported as `LaunchKitInstance`, and every option object has a
85
86
  named type: `AppOpenOptions`, `AndroidAppInfo`, `IOSAppInfo`, `WindowsAppInfo`,
86
87
  `MacOSAppInfo`, `TelephoneOptions`, `MessageOptions`, `MailOptions`, `MapOptions`,
87
- `FilepickerOptions`. `SettingType` is a value export; `AppOpenedBy` is a type.
88
+ `FilepickerOptions`, `EscapeWebviewOptions`. `SettingType` is a value export;
89
+ `AppOpenedBy` is a type.
88
90
 
89
91
  ```ts
90
92
  import LaunchKit, {
@@ -316,6 +318,34 @@ if (LaunchKit.utils.canOpenSetting) {
316
318
  }
317
319
  ```
318
320
 
321
+ ## Escaping in-app browsers
322
+
323
+ `escapeWebview()` reopens a URL — the current page by default — in the external
324
+ default browser, escaping the in-app webview the page is trapped in.
325
+
326
+ ```js
327
+ import LaunchKit from 'web-launch-kit'
328
+
329
+ if (LaunchKit.utils.canEscapeWebview) {
330
+ await LaunchKit.escapeWebview()
331
+ }
332
+
333
+ // Or send a specific URL out instead of the current page
334
+ await LaunchKit.escapeWebview({ url: 'https://example.com/checkout' })
335
+ ```
336
+
337
+ The route depends on the environment. KakaoTalk exposes a dedicated
338
+ `kakaotalk://web/openExternal` scheme and LINE honors the documented
339
+ `openExternalBrowser=1` query parameter — both work on Android and iOS. Other
340
+ webviews fall back to the OS route: a Chrome-pinned `intent://` URL on Android
341
+ (the original URL rides along as `S.browser_fallback_url`, so a Chrome-less
342
+ device stays in place), and the undocumented `x-safari-` scheme on iOS — which
343
+ works on iOS 15 and 17+ but not 16, always opens Safari regardless of the
344
+ default browser, and is blocked inside Meta's webviews (Facebook, Instagram).
345
+ Only http(s) URLs can be reopened. Outside a webview, or where no route is
346
+ available (hard-blocked webviews, iOS 16, desktop), the promise rejects — gate
347
+ the button on `utils.canEscapeWebview`.
348
+
319
349
  ---
320
350
 
321
351
  ## Notes
package/dist/index.d.ts CHANGED
@@ -93,10 +93,14 @@ declare interface MapOptions {
93
93
  fallback?: URLStringOrFallback;
94
94
  onAttempt?: (attempt: LaunchAttempt) => void;
95
95
  }
96
+ declare interface EscapeWebviewOptions {
97
+ url?: URLCandidate;
98
+ }
96
99
  interface LaunchKitUtils {
97
100
  get canOpenIntent(): boolean;
98
101
  get canOpenUniversal(): boolean;
99
102
  get canOpenSetting(): boolean;
103
+ get canEscapeWebview(): boolean;
100
104
  getTrackId(bundleId: string): Promise<string | undefined>;
101
105
  getProductId(packageFamilyName: string): Promise<string | undefined>;
102
106
  }
@@ -113,8 +117,9 @@ interface LaunchKitInstance {
113
117
  map(options?: MapOptions): Promise<AppOpenedBy>;
114
118
  filepicker(options?: FilepickerOptions): Promise<File[]>;
115
119
  setting(type?: SettingType): Promise<void>;
120
+ escapeWebview(options?: EscapeWebviewOptions): Promise<void>;
116
121
  }
117
122
  declare const LaunchKit: LaunchKitInstance;
118
123
 
119
124
  export { SettingType, LaunchKit as default };
120
- export type { AndroidAppInfo, AppInfo, AppOpenOptions, AppOpenedBy, FilepickerOptions, IOSAppInfo, LaunchAttempt, LaunchError, LaunchErrorCode, LaunchKitInstance, MacOSAppInfo, MailOptions, MapOptions, MessageOptions, OpenPickerStartIn, TelephoneOptions, WindowsAppInfo };
125
+ export type { AndroidAppInfo, AppInfo, AppOpenOptions, AppOpenedBy, EscapeWebviewOptions, FilepickerOptions, IOSAppInfo, LaunchAttempt, LaunchError, LaunchErrorCode, LaunchKitInstance, MacOSAppInfo, MailOptions, MapOptions, MessageOptions, OpenPickerStartIn, TelephoneOptions, WindowsAppInfo };
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version$3 = "0.0.7";
5
+ var version$3 = "0.0.8";
6
6
  var packageJSON$3 = {
7
7
  version: version$3};
8
8
 
@@ -2526,6 +2526,34 @@ function isFullyBlockedInAppBrowser() {
2526
2526
  function isPartnerAppOnly() {
2527
2527
  return /weibo.*weibo__[\d.]+/i.test(PlatformKit.userAgent);
2528
2528
  }
2529
+ function canOpenXSafari() {
2530
+ if (PlatformKit.os.name !== 'ios')
2531
+ return false;
2532
+ var version = PlatformKit.os.version;
2533
+ if (PlatformKit.compareVersion(version, '15.0') < 0)
2534
+ return false;
2535
+ if (PlatformKit.compareVersion(version, '16.0') >= 0 && PlatformKit.compareVersion(version, '17.0') < 0)
2536
+ return false;
2537
+ var inAppBrowser = PlatformKit.inAppBrowser;
2538
+ if (inAppBrowser === 'facebook' || inAppBrowser === 'instagram')
2539
+ return false;
2540
+ return canOpenScheme();
2541
+ }
2542
+ function isInWebview() {
2543
+ return PlatformKit.inAppBrowser !== null || PlatformKit.webview;
2544
+ }
2545
+ function canEscapeWebview() {
2546
+ if (!isInWebview())
2547
+ return false;
2548
+ var inAppBrowser = PlatformKit.inAppBrowser;
2549
+ if (inAppBrowser === 'kakaotalk' || inAppBrowser === 'line')
2550
+ return true;
2551
+ if (PlatformKit.os.name === 'android')
2552
+ return canOpenIntent();
2553
+ if (PlatformKit.os.name === 'ios')
2554
+ return canOpenXSafari();
2555
+ return false;
2556
+ }
2529
2557
  function canOpenWindowsSetting(version) {
2530
2558
  if (version.length === 0)
2531
2559
  return false;
@@ -2623,6 +2651,24 @@ function createWebStoreURL(id, os) {
2623
2651
  return os;
2624
2652
  }
2625
2653
  }
2654
+ function appendURLParameter(url, parameter) {
2655
+ var index = url.indexOf('#');
2656
+ var base = index === -1 ? url : url.substring(0, index);
2657
+ var hash = index === -1 ? '' : url.substring(index);
2658
+ return base + (base.indexOf('?') === -1 ? '?' : '&') + parameter + hash;
2659
+ }
2660
+ function createEscapeWebviewURL(url) {
2661
+ var inAppBrowser = PlatformKit.inAppBrowser;
2662
+ if (inAppBrowser === 'kakaotalk')
2663
+ return 'kakaotalk://web/openExternal?url=' + escapeURIComponentString(url);
2664
+ if (inAppBrowser === 'line')
2665
+ return appendURLParameter(url, 'openExternalBrowser=1');
2666
+ if (PlatformKit.os.name === 'android' && canOpenIntent())
2667
+ return createIntentURL(url, 'com.android.chrome', url);
2668
+ if (PlatformKit.os.name === 'ios' && canOpenXSafari())
2669
+ return 'x-safari-' + url;
2670
+ return undefined;
2671
+ }
2626
2672
  function escapeURIComponentString(value) {
2627
2673
  return encodeURIComponent(value)
2628
2674
  .replace(/[!'()*]/g, function (char) {
@@ -2896,6 +2942,9 @@ var LaunchKit = {
2896
2942
  get canOpenSetting() {
2897
2943
  return canOpenSetting();
2898
2944
  },
2945
+ get canEscapeWebview() {
2946
+ return canEscapeWebview();
2947
+ },
2899
2948
  getTrackId: getTrackIdAsync,
2900
2949
  getProductId: getProductIdAsync,
2901
2950
  },
@@ -3163,6 +3212,18 @@ var LaunchKit = {
3163
3212
  return openURLSequential();
3164
3213
  });
3165
3214
  },
3215
+ escapeWebview: function (options) {
3216
+ if (options === void 0) { options = {}; }
3217
+ if (!isInWebview())
3218
+ return Promise.reject(new Error('Escaping the webview is not needed: the current environment is not an in-app browser or webview. (userAgent: "' + PlatformKit.userAgent + '")'));
3219
+ var url = typeof options.url !== 'undefined' ? stripURL(options.url) : getTopmostWindow().location.href;
3220
+ if (!isWebURL(url))
3221
+ return Promise.reject(new Error('Cannot escape the webview: only http(s) URLs can be reopened in an external browser. (url: "' + url + '")'));
3222
+ var escape = createEscapeWebviewURL(url);
3223
+ if (typeof escape === 'undefined')
3224
+ return Promise.reject(new Error('Failed to escape the webview: no escape route is available in the current environment. (userAgent: "' + PlatformKit.userAgent + '")'));
3225
+ return openURL(0, escape, getDefaultTimeout(), isWebURL(escape));
3226
+ },
3166
3227
  };
3167
3228
 
3168
3229
  exports.default = LaunchKit;