@symbiote-native/web-browser 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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/core/index.d.ts +3 -0
  6. package/build/core/index.js +2 -0
  7. package/build/core/native-module.d.ts +12 -0
  8. package/build/core/native-module.js +3 -0
  9. package/build/core/types.d.ts +193 -0
  10. package/build/core/types.js +37 -0
  11. package/build/core/web-browser.d.ts +83 -0
  12. package/build/core/web-browser.js +256 -0
  13. package/build/react/index.d.ts +1 -0
  14. package/build/react/index.js +7 -0
  15. package/build/vue/index.d.ts +1 -0
  16. package/build/vue/index.js +4 -0
  17. package/build-ngc/angular/index.d.ts +1 -0
  18. package/build-ngc/angular/index.js +5 -0
  19. package/build-ngc/angular/index.js.map +1 -0
  20. package/build-ngc/core/index.d.ts +3 -0
  21. package/build-ngc/core/index.js +3 -0
  22. package/build-ngc/core/index.js.map +1 -0
  23. package/build-ngc/core/native-module.d.ts +12 -0
  24. package/build-ngc/core/native-module.js +4 -0
  25. package/build-ngc/core/native-module.js.map +1 -0
  26. package/build-ngc/core/types.d.ts +193 -0
  27. package/build-ngc/core/types.js +38 -0
  28. package/build-ngc/core/types.js.map +1 -0
  29. package/build-ngc/core/web-browser.d.ts +83 -0
  30. package/build-ngc/core/web-browser.js +257 -0
  31. package/build-ngc/core/web-browser.js.map +1 -0
  32. package/native-link.json +12 -0
  33. package/package.json +107 -0
  34. package/src/angular/index.ts +4 -0
  35. package/src/core/index.ts +25 -0
  36. package/src/core/native-module.ts +43 -0
  37. package/src/core/types.ts +208 -0
  38. package/src/core/web-browser.test.ts +295 -0
  39. package/src/core/web-browser.ts +316 -0
  40. package/src/react/index.ts +7 -0
  41. package/src/vue/index.ts +4 -0
@@ -0,0 +1,256 @@
1
+ // Hand-ported from .vendors/expo/packages/expo-web-browser/src/WebBrowser.ts (sdk-57). Two
2
+ // deliberate departures from upstream, both because SymbioteNative has no web target:
3
+ // `maybeCompleteAuthSession` is not ported (it exists only to close a `window.open` popup; on a
4
+ // native platform it can do nothing but return `{ type: 'failed' }`), and the web-only open
5
+ // options are gone from IWebBrowserOpenOptions.
6
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
7
+ import { AppState, Linking, processColor } from 'react-native';
8
+ import { expoWebBrowser } from './native-module';
9
+ import { WebBrowserResultType, } from './types';
10
+ const NATIVE_MODULE_NAME = 'expo-web-browser';
11
+ const EMPTY_CUSTOM_TABS_PACKAGES = {
12
+ defaultBrowserPackage: undefined,
13
+ preferredBrowserPackage: undefined,
14
+ browserPackages: [],
15
+ servicePackages: [],
16
+ };
17
+ /**
18
+ * The packages that support Custom Tabs, the Custom Tabs service, and the user's chosen and
19
+ * preferred browser. Only as reliable as `PackageManager.getResolvingActivities` underneath — a
20
+ * browser can be missing from `browserPackages` once another one is set as default.
21
+ *
22
+ * @platform android
23
+ */
24
+ export async function getCustomTabsSupportingBrowsersAsync() {
25
+ // iOS registers its no-op stub as `getCustomTabsSupportingBrowsers`, without the `Async`
26
+ // suffix, so this guard — kept in upstream's order deliberately — fires there before the
27
+ // non-Android early return below can be reached.
28
+ if (!expoWebBrowser.getCustomTabsSupportingBrowsersAsync) {
29
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getCustomTabsSupportingBrowsersAsync');
30
+ }
31
+ if (Platform.OS !== 'android') {
32
+ return EMPTY_CUSTOM_TABS_PACKAGES;
33
+ }
34
+ return expoWebBrowser.getCustomTabsSupportingBrowsersAsync();
35
+ }
36
+ /**
37
+ * Calls `warmUp` on the [`CustomTabsClient`](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsClient#warmup(long))
38
+ * for the given package.
39
+ *
40
+ * @param browserPackage Browser to warm up. Defaults to the preferred one.
41
+ * @platform android
42
+ */
43
+ export async function warmUpAsync(browserPackage) {
44
+ if (!expoWebBrowser.warmUpAsync) {
45
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'warmUpAsync');
46
+ }
47
+ if (Platform.OS !== 'android') {
48
+ return {};
49
+ }
50
+ return expoWebBrowser.warmUpAsync(browserPackage);
51
+ }
52
+ /**
53
+ * Starts a [`CustomTabsSession`](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#mayLaunchUrl(android.net.Uri,android.os.Bundle,java.util.List%3Candroid.os.Bundle%3E))
54
+ * if needed and calls its `mayLaunchUrl`, so the browser can prefetch the page.
55
+ *
56
+ * @param url The page most likely to be opened first.
57
+ * @param browserPackage Browser to inform. Defaults to the preferred one.
58
+ * @platform android
59
+ */
60
+ export async function mayInitWithUrlAsync(url, browserPackage) {
61
+ if (!expoWebBrowser.mayInitWithUrlAsync) {
62
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'mayInitWithUrlAsync');
63
+ }
64
+ if (Platform.OS !== 'android') {
65
+ return {};
66
+ }
67
+ return expoWebBrowser.mayInitWithUrlAsync(url, browserPackage);
68
+ }
69
+ /**
70
+ * Drops every binding `warmUpAsync` and `mayInitWithUrlAsync` created. Call it once the bindings
71
+ * are no longer needed to avoid leaking them — though they are also released when the app is
72
+ * destroyed, which is often good enough.
73
+ *
74
+ * @param browserPackage Browser to cool down. Defaults to the preferred one.
75
+ * @returns The cooled service, or an empty object when there was no connection to dismiss.
76
+ * @platform android
77
+ */
78
+ export async function coolDownAsync(browserPackage) {
79
+ if (!expoWebBrowser.coolDownAsync) {
80
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'coolDownAsync');
81
+ }
82
+ if (Platform.OS !== 'android') {
83
+ return {};
84
+ }
85
+ return expoWebBrowser.coolDownAsync(browserPackage);
86
+ }
87
+ /**
88
+ * Opens the url in a modal [`SFSafariViewController`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller)
89
+ * on iOS and a [Custom Tab](https://developer.chrome.com/docs/android/custom-tabs) on Android. The
90
+ * modal Safari does not share cookies with the system Safari — for a login flow that needs them,
91
+ * use `openAuthSessionAsync`.
92
+ *
93
+ * @returns Android resolves with `{ type: 'opened' }` as soon as the tab launches, without waiting
94
+ * for the user to close it. iOS resolves with `{ type: 'cancel' }` when the user dismissed the
95
+ * browser and `{ type: 'dismiss' }` when `dismissBrowser` closed it.
96
+ */
97
+ export async function openBrowserAsync(url, browserParams = {}) {
98
+ if (!expoWebBrowser.openBrowserAsync) {
99
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'openBrowserAsync');
100
+ }
101
+ return expoWebBrowser.openBrowserAsync(url, processOptions(browserParams));
102
+ }
103
+ /**
104
+ * Dismisses the presented browser. Throws where dismissing isn't available — Android's Custom Tabs
105
+ * offer no way to close a tab programmatically, so there the user has to press the tab's own close
106
+ * button.
107
+ *
108
+ * @platform ios
109
+ */
110
+ export function dismissBrowser() {
111
+ if (!expoWebBrowser.dismissBrowser) {
112
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'dismissBrowser');
113
+ }
114
+ return expoWebBrowser.dismissBrowser();
115
+ }
116
+ /**
117
+ * Opens a login page and resolves once the authentication provider redirects back to the app.
118
+ *
119
+ * On iOS this is `ASWebAuthenticationSession`, which asks the user whether the app may
120
+ * authenticate with the given url; the redirect URI registered with the authentication server has
121
+ * to use the app's own scheme (`demo://…`, not `https://…`). Adding a `Linking` listener of your
122
+ * own is unnecessary there and can have side effects.
123
+ *
124
+ * Android has no equivalent native API, so it is polyfilled: a Custom Tab plus `AppState` and
125
+ * `Linking`, racing "the deep link came back" against "the app returned to the foreground". Only
126
+ * one such session can be open at a time.
127
+ *
128
+ * @param url The login page to open.
129
+ * @param redirectUrl The url that deep-links back into the app. Without it the session can only
130
+ * end by the user closing the browser.
131
+ * @returns `{ type: 'success', url }` with the redirect url on a completed login,
132
+ * `{ type: 'cancel' }` when the user declined or closed the browser, and `{ type: 'dismiss' }` when
133
+ * `dismissBrowser` closed it.
134
+ */
135
+ export async function openAuthSessionAsync(url, redirectUrl, options = {}) {
136
+ if (!isAuthSessionNativelySupported()) {
137
+ return openAuthSessionPolyfillAsync(url, redirectUrl, options);
138
+ }
139
+ if (!expoWebBrowser.openAuthSessionAsync) {
140
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'openAuthSessionAsync');
141
+ }
142
+ return expoWebBrowser.openAuthSessionAsync(url, redirectUrl, processOptions(options));
143
+ }
144
+ /**
145
+ * Dismisses the current authentication session. Falls back to dismissing the plain browser where
146
+ * there is no native auth session, which on Android throws for the same reason `dismissBrowser`
147
+ * does.
148
+ *
149
+ * @platform ios
150
+ */
151
+ export function dismissAuthSession() {
152
+ if (isAuthSessionNativelySupported()) {
153
+ if (!expoWebBrowser.dismissAuthSession) {
154
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'dismissAuthSession');
155
+ }
156
+ expoWebBrowser.dismissAuthSession();
157
+ return;
158
+ }
159
+ if (!expoWebBrowser.dismissBrowser) {
160
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'dismissBrowser');
161
+ }
162
+ void expoWebBrowser.dismissBrowser();
163
+ }
164
+ function processOptions(options) {
165
+ return {
166
+ ...options,
167
+ controlsColor: processColor(options.controlsColor),
168
+ toolbarColor: processColor(options.toolbarColor),
169
+ secondaryToolbarColor: processColor(options.secondaryToolbarColor),
170
+ };
171
+ }
172
+ /* Android polyfill for the ASWebAuthenticationSession flow */
173
+ function isAuthSessionNativelySupported() {
174
+ return Platform.OS !== 'android';
175
+ }
176
+ let redirectSubscription = null;
177
+ // `openBrowserAsync` on Android resolves the moment the Custom Tab launches, not when the user
178
+ // closes it, so the polyfill has to watch AppState instead. This holds the `resolve` of the
179
+ // promise that waits for the app to come back to the foreground.
180
+ let onWebBrowserCloseAndroid = null;
181
+ // A null initial `AppState.currentState` means the first `change` event is the bridge reporting
182
+ // the state it captured, not a real transition — that one has to be ignored, or the session would
183
+ // end the instant it started. See https://reactnative.dev/docs/appstate#basic-usage.
184
+ let isAppStateAvailable = AppState.currentState !== null;
185
+ function onAppStateChangeAndroid(state) {
186
+ if (!isAppStateAvailable) {
187
+ isAppStateAvailable = true;
188
+ return;
189
+ }
190
+ if (state === 'active' && onWebBrowserCloseAndroid) {
191
+ onWebBrowserCloseAndroid();
192
+ }
193
+ }
194
+ async function openBrowserAndWaitAndroidAsync(startUrl, browserParams = {}) {
195
+ const appStateChangedToActive = new Promise(resolve => {
196
+ onWebBrowserCloseAndroid = resolve;
197
+ });
198
+ const stateChangeSubscription = AppState.addEventListener('change', onAppStateChangeAndroid);
199
+ let launched;
200
+ try {
201
+ launched = await openBrowserAsync(startUrl, browserParams);
202
+ }
203
+ catch (error) {
204
+ stateChangeSubscription.remove();
205
+ onWebBrowserCloseAndroid = null;
206
+ throw error;
207
+ }
208
+ let result = { type: WebBrowserResultType.CANCEL };
209
+ if (launched.type === WebBrowserResultType.OPENED) {
210
+ await appStateChangedToActive;
211
+ result = { type: WebBrowserResultType.DISMISS };
212
+ }
213
+ stateChangeSubscription.remove();
214
+ onWebBrowserCloseAndroid = null;
215
+ return result;
216
+ }
217
+ async function openAuthSessionPolyfillAsync(startUrl, returnUrl, browserParams = {}) {
218
+ if (redirectSubscription) {
219
+ throw new Error("The WebBrowser's auth session is in an invalid state with a redirect handler set when it should not be");
220
+ }
221
+ if (onWebBrowserCloseAndroid) {
222
+ throw new Error('WebBrowser is already open, only one can be open at a time');
223
+ }
224
+ try {
225
+ return await Promise.race([
226
+ openBrowserAndWaitAndroidAsync(startUrl, browserParams),
227
+ waitForRedirectAsync(returnUrl),
228
+ ]);
229
+ }
230
+ finally {
231
+ // Only reachable on a platform that can dismiss a browser at all — Android users have to
232
+ // close the Custom Tab themselves.
233
+ if (expoWebBrowser.dismissBrowser) {
234
+ void expoWebBrowser.dismissBrowser();
235
+ }
236
+ stopWaitingForRedirect();
237
+ }
238
+ }
239
+ function stopWaitingForRedirect() {
240
+ if (!redirectSubscription) {
241
+ throw new Error('The WebBrowser auth session is in an invalid state with no redirect handler when one should be set');
242
+ }
243
+ redirectSubscription.remove();
244
+ redirectSubscription = null;
245
+ }
246
+ function waitForRedirectAsync(returnUrl) {
247
+ // Deliberately never resolves when `returnUrl` is nullish: the browser promise it is raced
248
+ // against is then the only thing that can settle the session.
249
+ return new Promise(resolve => {
250
+ redirectSubscription = Linking.addEventListener('url', (event) => {
251
+ if (returnUrl && event.url.startsWith(returnUrl)) {
252
+ resolve({ url: event.url, type: 'success' });
253
+ }
254
+ });
255
+ });
256
+ }
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/web-browser/react: the React entry over the framework-agnostic core.
2
+ // Every export is a stateless free function. The one piece of live state — the Android
3
+ // auth-session polyfill's redirect subscription — belongs to a single in-flight promise inside the
4
+ // core and never surfaces as something a caller subscribes to or tears down, so there is nothing
5
+ // for a hook to wrap. Hence a plain re-export, the same shape packages/secure-store's React entry
6
+ // has for the same reason.
7
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/web-browser/vue: the Vue entry over the framework-agnostic core.
2
+ // Nothing here holds per-instance state or hands back a subscription, so there is no lifecycle for
3
+ // a composable to own — see the React entry for the full reasoning.
4
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/web-browser/angular: the Angular entry over the framework-agnostic core.
2
+ // Nothing here holds per-instance state or hands back a subscription, so there is no lifecycle for
3
+ // an injectable service to own — see the React entry for the full reasoning.
4
+ export * from '../core';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,mGAAmG;AACnG,6EAA6E;AAC7E,cAAc,SAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { getCustomTabsSupportingBrowsersAsync, warmUpAsync, mayInitWithUrlAsync, coolDownAsync, openBrowserAsync, dismissBrowser, openAuthSessionAsync, dismissAuthSession, } from './web-browser';
2
+ export { WebBrowserResultType, WebBrowserPresentationStyle } from './types';
3
+ export type { IAuthSessionOpenOptions, IRedirectEvent, IServiceActionResult, IWebBrowserAuthSessionResult, IWebBrowserCoolDownResult, IWebBrowserCustomTabsResults, IWebBrowserDismissResult, IWebBrowserMayInitWithUrlResult, IWebBrowserOpenOptions, IWebBrowserRedirectResult, IWebBrowserResult, IWebBrowserWarmUpResult, } from './types';
@@ -0,0 +1,3 @@
1
+ export { getCustomTabsSupportingBrowsersAsync, warmUpAsync, mayInitWithUrlAsync, coolDownAsync, openBrowserAsync, dismissBrowser, openAuthSessionAsync, dismissAuthSession, } from './web-browser';
2
+ export { WebBrowserResultType, WebBrowserPresentationStyle } from './types';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,WAAW,EACX,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { IProcessedOpenOptions, IWebBrowserAuthSessionResult, IWebBrowserCoolDownResult, IWebBrowserCustomTabsResults, IWebBrowserDismissResult, IWebBrowserMayInitWithUrlResult, IWebBrowserResult, IWebBrowserWarmUpResult } from './types';
2
+ export type INativeWebBrowserModule = {
3
+ openBrowserAsync?(url: string, options: IProcessedOpenOptions): Promise<IWebBrowserResult>;
4
+ dismissBrowser?(): Promise<IWebBrowserDismissResult>;
5
+ openAuthSessionAsync?(url: string, redirectUrl: string | null | undefined, options: IProcessedOpenOptions): Promise<IWebBrowserAuthSessionResult>;
6
+ dismissAuthSession?(): void;
7
+ warmUpAsync?(browserPackage?: string): Promise<IWebBrowserWarmUpResult>;
8
+ coolDownAsync?(browserPackage?: string): Promise<IWebBrowserCoolDownResult>;
9
+ mayInitWithUrlAsync?(url: string, browserPackage?: string): Promise<IWebBrowserMayInitWithUrlResult>;
10
+ getCustomTabsSupportingBrowsersAsync?(): Promise<IWebBrowserCustomTabsResults>;
11
+ };
12
+ export declare const expoWebBrowser: INativeWebBrowserModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_WEB_BROWSER_MODULE_NAME = 'ExpoWebBrowser';
3
+ export const expoWebBrowser = requireNativeModule(EXPO_WEB_BROWSER_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAYxD,MAAM,4BAA4B,GAAG,gBAAgB,CAAC;AA4BtD,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAC/C,4BAA4B,CAC7B,CAAC"}
@@ -0,0 +1,193 @@
1
+ import type { ProcessedColorValue } from 'react-native';
2
+ /** Payload of the deep-link event the Android auth-session polyfill listens for. */
3
+ export type IRedirectEvent = {
4
+ url: string;
5
+ };
6
+ /**
7
+ * Options for [`openBrowserAsync`](#openbrowserasync).
8
+ *
9
+ * Upstream's two `@platform web` fields (`windowName`, `windowFeatures`) are not part of this
10
+ * type — SymbioteNative has no web target, so they could never be read.
11
+ */
12
+ export type IWebBrowserOpenOptions = {
13
+ /** Color of the toolbar. Accepts any React Native color format. */
14
+ toolbarColor?: string;
15
+ /**
16
+ * Package name of the browser that should handle the Custom Tab. Pick one from
17
+ * `getCustomTabsSupportingBrowsersAsync`.
18
+ * @platform android
19
+ */
20
+ browserPackage?: string;
21
+ /** Whether the toolbar hides as the user scrolls the page. */
22
+ enableBarCollapsing?: boolean;
23
+ /**
24
+ * Color of the secondary toolbar. Accepts any React Native color format.
25
+ * @platform android
26
+ */
27
+ secondaryToolbarColor?: string;
28
+ /**
29
+ * Whether the toolbar shows the website's title.
30
+ * @platform android
31
+ */
32
+ showTitle?: boolean;
33
+ /**
34
+ * Whether a default share item is added to the browser's menu.
35
+ * @platform android
36
+ */
37
+ enableDefaultShareMenuItem?: boolean;
38
+ /**
39
+ * Whether the browsed website appears as its own entry in the recents/multitasking view.
40
+ * Requires `createTask` to stay `true`.
41
+ * @default false
42
+ * @platform android
43
+ */
44
+ showInRecents?: boolean;
45
+ /**
46
+ * Whether the browser opens in its own task rather than the app's.
47
+ * @default true
48
+ * @platform android
49
+ */
50
+ createTask?: boolean;
51
+ /**
52
+ * Whether to launch the browser through a transparent proxy activity with a different task
53
+ * affinity, which keeps the browser alive while the app is backgrounded. Only read when
54
+ * `createTask` is `true`, and forces `showInRecents` on. Set `false` for the legacy direct
55
+ * launch.
56
+ * @default true
57
+ * @platform android
58
+ */
59
+ useProxyActivity?: boolean;
60
+ /**
61
+ * Tint color for the controls in `SFSafariViewController`. Accepts any React Native color
62
+ * format.
63
+ * @platform ios
64
+ */
65
+ controlsColor?: string;
66
+ /**
67
+ * Style of the dismiss button.
68
+ * @platform ios
69
+ */
70
+ dismissButtonStyle?: 'done' | 'close' | 'cancel';
71
+ /**
72
+ * Whether Safari should enter Reader mode when the page supports it.
73
+ * @platform ios
74
+ */
75
+ readerMode?: boolean;
76
+ /**
77
+ * [Presentation style](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle)
78
+ * of the browser window.
79
+ * @default WebBrowserPresentationStyle.OVER_FULL_SCREEN
80
+ * @platform ios
81
+ */
82
+ presentationStyle?: WebBrowserPresentationStyle;
83
+ };
84
+ /**
85
+ * Options for [`openAuthSessionAsync`](#openauthsessionasync). Android has no native auth-session
86
+ * API, so there the inherited `IWebBrowserOpenOptions` fields are passed to the Custom Tab the
87
+ * polyfill opens; on iOS `ASWebAuthenticationSession` ignores them and reads only the two fields
88
+ * below.
89
+ */
90
+ export type IAuthSessionOpenOptions = IWebBrowserOpenOptions & {
91
+ /**
92
+ * Ask the browser for a private authentication session, so it shares no cookies or browsing
93
+ * data with the user's normal session. Whether the request is honored is up to the user's
94
+ * default browser.
95
+ * @default false
96
+ * @platform ios
97
+ */
98
+ preferEphemeralSession?: boolean;
99
+ /**
100
+ * Use HTTPS universal-link callbacks instead of custom-URL-scheme callbacks. Needs the
101
+ * Associated Domains entitlement configured for the redirect URL's host, and iOS 17.4+. Left
102
+ * `false`, the legacy `callbackURLScheme` API is used, which needs no entitlement.
103
+ * @default false
104
+ * @platform ios
105
+ */
106
+ preferUniversalLinks?: boolean;
107
+ };
108
+ /**
109
+ * The open options after the three color fields have been run through `processColor`. This is the
110
+ * shape the native module actually receives — a color reaches native as a platform integer, never
111
+ * as the CSS string the caller wrote.
112
+ */
113
+ export type IProcessedOpenOptions = Omit<IAuthSessionOpenOptions, 'toolbarColor' | 'secondaryToolbarColor' | 'controlsColor'> & {
114
+ toolbarColor?: ProcessedColorValue | null;
115
+ secondaryToolbarColor?: ProcessedColorValue | null;
116
+ controlsColor?: ProcessedColorValue | null;
117
+ };
118
+ export type IWebBrowserCustomTabsResults = {
119
+ /**
120
+ * The browser the user picked as their device default, or `undefined` when there is none — which
121
+ * usually means the user will be prompted to choose.
122
+ */
123
+ defaultBrowserPackage?: string;
124
+ /**
125
+ * The browser `CustomTabsClient` prefers for Custom Tabs. Favors the user's default as long as
126
+ * it appears in both `browserPackages` and `servicePackages`; only such browsers fully support
127
+ * Custom Tabs. `undefined` when no installed browser qualifies.
128
+ */
129
+ preferredBrowserPackage?: string;
130
+ /**
131
+ * Every package `PackageManager` recognizes as able to handle Custom Tabs. Empty when the device
132
+ * has no supporting browser.
133
+ */
134
+ browserPackages: string[];
135
+ /**
136
+ * Every package `PackageManager` recognizes as able to handle the Custom Tabs *Service* — the
137
+ * one `warmUpAsync`, `mayInitWithUrlAsync` and `coolDownAsync` talk to.
138
+ */
139
+ servicePackages: string[];
140
+ };
141
+ /** How a browser session ended. */
142
+ export declare enum WebBrowserResultType {
143
+ /** The user dismissed the browser themselves. @platform ios */
144
+ CANCEL = "cancel",
145
+ /** The browser was closed by a `dismissBrowser` call. @platform ios */
146
+ DISMISS = "dismiss",
147
+ /** The browser was launched. Android resolves here without waiting for it to close. @platform android */
148
+ OPENED = "opened",
149
+ /** Another browser session is already in progress. */
150
+ LOCKED = "locked"
151
+ }
152
+ /**
153
+ * A browser presentation style, mapped directly onto
154
+ * [`UIModalPresentationStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle).
155
+ *
156
+ * @platform ios
157
+ */
158
+ export declare enum WebBrowserPresentationStyle {
159
+ /** The browser covers the screen. */
160
+ FULL_SCREEN = "fullScreen",
161
+ /** The browser partially covers the underlying content. */
162
+ PAGE_SHEET = "pageSheet",
163
+ /** The browser is centered on the screen. */
164
+ FORM_SHEET = "formSheet",
165
+ /** The browser is displayed over the app's content. */
166
+ CURRENT_CONTEXT = "currentContext",
167
+ /** The browser view covers the screen. */
168
+ OVER_FULL_SCREEN = "overFullScreen",
169
+ /** The browser is displayed over the app's content. */
170
+ OVER_CURRENT_CONTEXT = "overCurrentContext",
171
+ /** The browser is displayed in a popover view. */
172
+ POPOVER = "popover",
173
+ /** The system picks the style. Older iOS versions fall back to `FULL_SCREEN`. */
174
+ AUTOMATIC = "automatic"
175
+ }
176
+ export type IWebBrowserResult = {
177
+ type: WebBrowserResultType;
178
+ };
179
+ /** What `dismissBrowser` resolves with once the presented browser has been torn down. */
180
+ export type IWebBrowserDismissResult = {
181
+ type: WebBrowserResultType.DISMISS;
182
+ };
183
+ export type IWebBrowserRedirectResult = {
184
+ type: 'success';
185
+ url: string;
186
+ };
187
+ export type IServiceActionResult = {
188
+ servicePackage?: string;
189
+ };
190
+ export type IWebBrowserAuthSessionResult = IWebBrowserRedirectResult | IWebBrowserResult;
191
+ export type IWebBrowserMayInitWithUrlResult = IServiceActionResult;
192
+ export type IWebBrowserWarmUpResult = IServiceActionResult;
193
+ export type IWebBrowserCoolDownResult = IServiceActionResult;
@@ -0,0 +1,38 @@
1
+ /** How a browser session ended. */
2
+ export var WebBrowserResultType;
3
+ (function (WebBrowserResultType) {
4
+ /** The user dismissed the browser themselves. @platform ios */
5
+ WebBrowserResultType["CANCEL"] = "cancel";
6
+ /** The browser was closed by a `dismissBrowser` call. @platform ios */
7
+ WebBrowserResultType["DISMISS"] = "dismiss";
8
+ /** The browser was launched. Android resolves here without waiting for it to close. @platform android */
9
+ WebBrowserResultType["OPENED"] = "opened";
10
+ /** Another browser session is already in progress. */
11
+ WebBrowserResultType["LOCKED"] = "locked";
12
+ })(WebBrowserResultType || (WebBrowserResultType = {}));
13
+ /**
14
+ * A browser presentation style, mapped directly onto
15
+ * [`UIModalPresentationStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle).
16
+ *
17
+ * @platform ios
18
+ */
19
+ export var WebBrowserPresentationStyle;
20
+ (function (WebBrowserPresentationStyle) {
21
+ /** The browser covers the screen. */
22
+ WebBrowserPresentationStyle["FULL_SCREEN"] = "fullScreen";
23
+ /** The browser partially covers the underlying content. */
24
+ WebBrowserPresentationStyle["PAGE_SHEET"] = "pageSheet";
25
+ /** The browser is centered on the screen. */
26
+ WebBrowserPresentationStyle["FORM_SHEET"] = "formSheet";
27
+ /** The browser is displayed over the app's content. */
28
+ WebBrowserPresentationStyle["CURRENT_CONTEXT"] = "currentContext";
29
+ /** The browser view covers the screen. */
30
+ WebBrowserPresentationStyle["OVER_FULL_SCREEN"] = "overFullScreen";
31
+ /** The browser is displayed over the app's content. */
32
+ WebBrowserPresentationStyle["OVER_CURRENT_CONTEXT"] = "overCurrentContext";
33
+ /** The browser is displayed in a popover view. */
34
+ WebBrowserPresentationStyle["POPOVER"] = "popover";
35
+ /** The system picks the style. Older iOS versions fall back to `FULL_SCREEN`. */
36
+ WebBrowserPresentationStyle["AUTOMATIC"] = "automatic";
37
+ })(WebBrowserPresentationStyle || (WebBrowserPresentationStyle = {}));
38
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAqJA,mCAAmC;AACnC,MAAM,CAAN,IAAY,oBASX;AATD,WAAY,oBAAoB;IAC9B,+DAA+D;IAC/D,yCAAiB,CAAA;IACjB,uEAAuE;IACvE,2CAAmB,CAAA;IACnB,yGAAyG;IACzG,yCAAiB,CAAA;IACjB,sDAAsD;IACtD,yCAAiB,CAAA;AACnB,CAAC,EATW,oBAAoB,KAApB,oBAAoB,QAS/B;AAED;;;;;GAKG;AACH,MAAM,CAAN,IAAY,2BAiBX;AAjBD,WAAY,2BAA2B;IACrC,qCAAqC;IACrC,yDAA0B,CAAA;IAC1B,2DAA2D;IAC3D,uDAAwB,CAAA;IACxB,6CAA6C;IAC7C,uDAAwB,CAAA;IACxB,uDAAuD;IACvD,iEAAkC,CAAA;IAClC,0CAA0C;IAC1C,kEAAmC,CAAA;IACnC,uDAAuD;IACvD,0EAA2C,CAAA;IAC3C,kDAAkD;IAClD,kDAAmB,CAAA;IACnB,iFAAiF;IACjF,sDAAuB,CAAA;AACzB,CAAC,EAjBW,2BAA2B,KAA3B,2BAA2B,QAiBtC"}
@@ -0,0 +1,83 @@
1
+ import { type IAuthSessionOpenOptions, type IWebBrowserAuthSessionResult, type IWebBrowserCoolDownResult, type IWebBrowserCustomTabsResults, type IWebBrowserDismissResult, type IWebBrowserMayInitWithUrlResult, type IWebBrowserOpenOptions, type IWebBrowserResult, type IWebBrowserWarmUpResult } from './types';
2
+ /**
3
+ * The packages that support Custom Tabs, the Custom Tabs service, and the user's chosen and
4
+ * preferred browser. Only as reliable as `PackageManager.getResolvingActivities` underneath — a
5
+ * browser can be missing from `browserPackages` once another one is set as default.
6
+ *
7
+ * @platform android
8
+ */
9
+ export declare function getCustomTabsSupportingBrowsersAsync(): Promise<IWebBrowserCustomTabsResults>;
10
+ /**
11
+ * Calls `warmUp` on the [`CustomTabsClient`](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsClient#warmup(long))
12
+ * for the given package.
13
+ *
14
+ * @param browserPackage Browser to warm up. Defaults to the preferred one.
15
+ * @platform android
16
+ */
17
+ export declare function warmUpAsync(browserPackage?: string): Promise<IWebBrowserWarmUpResult>;
18
+ /**
19
+ * Starts a [`CustomTabsSession`](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#mayLaunchUrl(android.net.Uri,android.os.Bundle,java.util.List%3Candroid.os.Bundle%3E))
20
+ * if needed and calls its `mayLaunchUrl`, so the browser can prefetch the page.
21
+ *
22
+ * @param url The page most likely to be opened first.
23
+ * @param browserPackage Browser to inform. Defaults to the preferred one.
24
+ * @platform android
25
+ */
26
+ export declare function mayInitWithUrlAsync(url: string, browserPackage?: string): Promise<IWebBrowserMayInitWithUrlResult>;
27
+ /**
28
+ * Drops every binding `warmUpAsync` and `mayInitWithUrlAsync` created. Call it once the bindings
29
+ * are no longer needed to avoid leaking them — though they are also released when the app is
30
+ * destroyed, which is often good enough.
31
+ *
32
+ * @param browserPackage Browser to cool down. Defaults to the preferred one.
33
+ * @returns The cooled service, or an empty object when there was no connection to dismiss.
34
+ * @platform android
35
+ */
36
+ export declare function coolDownAsync(browserPackage?: string): Promise<IWebBrowserCoolDownResult>;
37
+ /**
38
+ * Opens the url in a modal [`SFSafariViewController`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller)
39
+ * on iOS and a [Custom Tab](https://developer.chrome.com/docs/android/custom-tabs) on Android. The
40
+ * modal Safari does not share cookies with the system Safari — for a login flow that needs them,
41
+ * use `openAuthSessionAsync`.
42
+ *
43
+ * @returns Android resolves with `{ type: 'opened' }` as soon as the tab launches, without waiting
44
+ * for the user to close it. iOS resolves with `{ type: 'cancel' }` when the user dismissed the
45
+ * browser and `{ type: 'dismiss' }` when `dismissBrowser` closed it.
46
+ */
47
+ export declare function openBrowserAsync(url: string, browserParams?: IWebBrowserOpenOptions): Promise<IWebBrowserResult>;
48
+ /**
49
+ * Dismisses the presented browser. Throws where dismissing isn't available — Android's Custom Tabs
50
+ * offer no way to close a tab programmatically, so there the user has to press the tab's own close
51
+ * button.
52
+ *
53
+ * @platform ios
54
+ */
55
+ export declare function dismissBrowser(): Promise<IWebBrowserDismissResult>;
56
+ /**
57
+ * Opens a login page and resolves once the authentication provider redirects back to the app.
58
+ *
59
+ * On iOS this is `ASWebAuthenticationSession`, which asks the user whether the app may
60
+ * authenticate with the given url; the redirect URI registered with the authentication server has
61
+ * to use the app's own scheme (`demo://…`, not `https://…`). Adding a `Linking` listener of your
62
+ * own is unnecessary there and can have side effects.
63
+ *
64
+ * Android has no equivalent native API, so it is polyfilled: a Custom Tab plus `AppState` and
65
+ * `Linking`, racing "the deep link came back" against "the app returned to the foreground". Only
66
+ * one such session can be open at a time.
67
+ *
68
+ * @param url The login page to open.
69
+ * @param redirectUrl The url that deep-links back into the app. Without it the session can only
70
+ * end by the user closing the browser.
71
+ * @returns `{ type: 'success', url }` with the redirect url on a completed login,
72
+ * `{ type: 'cancel' }` when the user declined or closed the browser, and `{ type: 'dismiss' }` when
73
+ * `dismissBrowser` closed it.
74
+ */
75
+ export declare function openAuthSessionAsync(url: string, redirectUrl?: string | null, options?: IAuthSessionOpenOptions): Promise<IWebBrowserAuthSessionResult>;
76
+ /**
77
+ * Dismisses the current authentication session. Falls back to dismissing the plain browser where
78
+ * there is no native auth session, which on Android throws for the same reason `dismissBrowser`
79
+ * does.
80
+ *
81
+ * @platform ios
82
+ */
83
+ export declare function dismissAuthSession(): void;