@symbiote-native/web-browser 0.0.1 → 0.2.0
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/build/angular/index.js +1 -1
- package/build/core/index.js +2 -2
- package/build/core/web-browser.js +2 -2
- package/build/react/index.js +1 -1
- package/build/svelte/index.d.ts +1 -0
- package/build/svelte/index.js +8 -0
- package/build/vue/index.js +1 -1
- package/package.json +30 -13
- package/src/svelte/index.ts +8 -0
- package/src/core/web-browser.test.ts +0 -295
package/build/angular/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @symbiote-native/web-browser/angular: the Angular entry over the framework-agnostic core.
|
|
2
2
|
// Nothing here holds per-instance state or hands back a subscription, so there is no lifecycle for
|
|
3
3
|
// an injectable service to own — see the React entry for the full reasoning.
|
|
4
|
-
export * from '../core';
|
|
4
|
+
export * from '../core/index.js';
|
package/build/core/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { getCustomTabsSupportingBrowsersAsync, warmUpAsync, mayInitWithUrlAsync, coolDownAsync, openBrowserAsync, dismissBrowser, openAuthSessionAsync, dismissAuthSession, } from './web-browser';
|
|
2
|
-
export { WebBrowserResultType, WebBrowserPresentationStyle } from './types';
|
|
1
|
+
export { getCustomTabsSupportingBrowsersAsync, warmUpAsync, mayInitWithUrlAsync, coolDownAsync, openBrowserAsync, dismissBrowser, openAuthSessionAsync, dismissAuthSession, } from './web-browser.js';
|
|
2
|
+
export { WebBrowserResultType, WebBrowserPresentationStyle } from './types.js';
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// options are gone from IWebBrowserOpenOptions.
|
|
6
6
|
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
7
7
|
import { AppState, Linking, processColor } from 'react-native';
|
|
8
|
-
import { expoWebBrowser } from './native-module';
|
|
9
|
-
import { WebBrowserResultType, } from './types';
|
|
8
|
+
import { expoWebBrowser } from './native-module.js';
|
|
9
|
+
import { WebBrowserResultType, } from './types.js';
|
|
10
10
|
const NATIVE_MODULE_NAME = 'expo-web-browser';
|
|
11
11
|
const EMPTY_CUSTOM_TABS_PACKAGES = {
|
|
12
12
|
defaultBrowserPackage: undefined,
|
package/build/react/index.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
// core and never surfaces as something a caller subscribes to or tears down, so there is nothing
|
|
5
5
|
// for a hook to wrap. Hence a plain re-export, the same shape packages/secure-store's React entry
|
|
6
6
|
// has for the same reason.
|
|
7
|
-
export * from '../core';
|
|
7
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @symbiote-native/web-browser/svelte: the Svelte 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
|
|
4
|
+
// the core and never surfaces as something a caller subscribes to or tears down, so there is
|
|
5
|
+
// nothing for a rune to wrap. Hence a plain re-export. Svelte's lifecycle bucket is `runes/`
|
|
6
|
+
// (adapters/svelte/src/runes — never React's `hooks` or Vue's `composables`), and this package
|
|
7
|
+
// has none to fill.
|
|
8
|
+
export * from '../core/index.js';
|
package/build/vue/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @symbiote-native/web-browser/vue: the Vue entry over the framework-agnostic core.
|
|
2
2
|
// Nothing here holds per-instance state or hands back a subscription, so there is no lifecycle for
|
|
3
3
|
// a composable to own — see the React entry for the full reasoning.
|
|
4
|
-
export * from '../core';
|
|
4
|
+
export * from '../core/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/web-browser",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "expo-web-browser wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. In-app browser (SFSafariViewController / Android Custom Tabs) and the OAuth auth-session flow.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"types": "./build/react/index.d.ts",
|
|
31
31
|
"default": "./build/react/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./svelte": {
|
|
34
|
+
"types": "./build/svelte/index.d.ts",
|
|
35
|
+
"default": "./build/svelte/index.js"
|
|
36
|
+
},
|
|
33
37
|
"./angular": {
|
|
34
38
|
"types": "./build-ngc/angular/index.d.ts",
|
|
35
39
|
"react-native": "./build-ngc/angular/index.js",
|
|
@@ -40,7 +44,10 @@
|
|
|
40
44
|
"src",
|
|
41
45
|
"build",
|
|
42
46
|
"build-ngc",
|
|
43
|
-
"native-link.json"
|
|
47
|
+
"native-link.json",
|
|
48
|
+
"!src/**/*.test.*",
|
|
49
|
+
"!src/**/*.spec.*",
|
|
50
|
+
"!src/**/*.detox.*"
|
|
44
51
|
],
|
|
45
52
|
"publishConfig": {
|
|
46
53
|
"access": "public"
|
|
@@ -50,15 +57,17 @@
|
|
|
50
57
|
"expo-modules-core": "57.0.5"
|
|
51
58
|
},
|
|
52
59
|
"peerDependencies": {
|
|
60
|
+
"@symbiote-native/angular": ">=0.6.2",
|
|
53
61
|
"@symbiote-native/engine": ">=0.1.7",
|
|
62
|
+
"@symbiote-native/react": ">=0.2.8",
|
|
63
|
+
"@symbiote-native/svelte": ">=0.1.0",
|
|
64
|
+
"@symbiote-native/vue": ">=0.3.8",
|
|
54
65
|
"@angular/core": ">=20",
|
|
55
66
|
"@vue/runtime-core": "^3.5.13",
|
|
56
67
|
"react": ">=19.0.0",
|
|
57
68
|
"react-native": ">=0.86",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"@symbiote-native/react": "0.2.8",
|
|
61
|
-
"@symbiote-native/vue": "0.3.8"
|
|
69
|
+
"svelte": ">=5.56.0",
|
|
70
|
+
"vue": ">=3.5.0"
|
|
62
71
|
},
|
|
63
72
|
"peerDependenciesMeta": {
|
|
64
73
|
"@symbiote-native/angular": {
|
|
@@ -67,6 +76,9 @@
|
|
|
67
76
|
"@symbiote-native/react": {
|
|
68
77
|
"optional": true
|
|
69
78
|
},
|
|
79
|
+
"@symbiote-native/svelte": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
70
82
|
"@symbiote-native/vue": {
|
|
71
83
|
"optional": true
|
|
72
84
|
},
|
|
@@ -79,24 +91,29 @@
|
|
|
79
91
|
"react": {
|
|
80
92
|
"optional": true
|
|
81
93
|
},
|
|
94
|
+
"svelte": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
82
97
|
"vue": {
|
|
83
98
|
"optional": true
|
|
84
99
|
}
|
|
85
100
|
},
|
|
86
101
|
"devDependencies": {
|
|
87
|
-
"@angular/compiler": "
|
|
88
|
-
"@angular/compiler-cli": "
|
|
89
|
-
"@angular/core": "
|
|
102
|
+
"@angular/compiler": "~22.0.8",
|
|
103
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
104
|
+
"@angular/core": "~22.0.8",
|
|
90
105
|
"@types/node": "^26.0.0",
|
|
91
106
|
"@types/react": "^19.2.0",
|
|
92
107
|
"@vue/runtime-core": "^3.5.13",
|
|
93
108
|
"react": "19.2.3",
|
|
109
|
+
"svelte": "^5.56.0",
|
|
94
110
|
"typescript": "~6.0.0",
|
|
111
|
+
"@symbiote-native/engine": "0.2.0",
|
|
112
|
+
"@symbiote-native/angular": "0.7.0",
|
|
113
|
+
"@symbiote-native/svelte": "0.2.0",
|
|
95
114
|
"@symbiote-native/test-utils": "0.1.6",
|
|
96
|
-
"@symbiote-native/
|
|
97
|
-
"@symbiote-native/vue": "0.
|
|
98
|
-
"@symbiote-native/engine": "0.1.7",
|
|
99
|
-
"@symbiote-native/react": "0.2.8"
|
|
115
|
+
"@symbiote-native/react": "0.3.0",
|
|
116
|
+
"@symbiote-native/vue": "0.4.0"
|
|
100
117
|
},
|
|
101
118
|
"scripts": {
|
|
102
119
|
"typecheck": "tsc --build",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @symbiote-native/web-browser/svelte: the Svelte 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
|
|
4
|
+
// the core and never surfaces as something a caller subscribes to or tears down, so there is
|
|
5
|
+
// nothing for a rune to wrap. Hence a plain re-export. Svelte's lifecycle bucket is `runes/`
|
|
6
|
+
// (adapters/svelte/src/runes — never React's `hooks` or Vue's `composables`), and this package
|
|
7
|
+
// has none to fill.
|
|
8
|
+
export * from '../core';
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
const FAKE_NATIVE_WEB_BROWSER = {
|
|
4
|
-
openBrowserAsync: vi.fn(async () => ({ type: 'opened' })),
|
|
5
|
-
dismissBrowser: vi.fn(async () => ({ type: 'dismiss' })),
|
|
6
|
-
openAuthSessionAsync: vi.fn(async () => ({ type: 'success', url: 'myapp://callback' })),
|
|
7
|
-
dismissAuthSession: vi.fn(() => undefined),
|
|
8
|
-
warmUpAsync: vi.fn(async () => ({ servicePackage: 'com.android.chrome' })),
|
|
9
|
-
coolDownAsync: vi.fn(async () => ({ servicePackage: 'com.android.chrome' })),
|
|
10
|
-
mayInitWithUrlAsync: vi.fn(async () => ({ servicePackage: 'com.android.chrome' })),
|
|
11
|
-
getCustomTabsSupportingBrowsersAsync: vi.fn(async () => ({
|
|
12
|
-
defaultBrowserPackage: 'com.android.chrome',
|
|
13
|
-
preferredBrowserPackage: 'com.android.chrome',
|
|
14
|
-
browserPackages: ['com.android.chrome'],
|
|
15
|
-
servicePackages: ['com.android.chrome'],
|
|
16
|
-
})),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const fakePlatform = { OS: 'ios' as 'ios' | 'android' };
|
|
20
|
-
|
|
21
|
-
// The real ExpoWebBrowser native module only exists on device — resolving it via
|
|
22
|
-
// requireNativeModule() at import time would throw in this headless run, so the module-lookup file
|
|
23
|
-
// is faked in place of expo-modules-core's runtime resolution, the same pattern
|
|
24
|
-
// packages/secure-store/src/core/secure-store.test.ts uses.
|
|
25
|
-
vi.mock('./native-module', () => ({
|
|
26
|
-
expoWebBrowser: FAKE_NATIVE_WEB_BROWSER,
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
// expo-modules-core's real entry transitively imports 'react-native', whose Flow-typed source
|
|
30
|
-
// Vitest's Oxc transform can't parse — so only the members used as values are faked.
|
|
31
|
-
vi.mock('expo-modules-core', () => ({
|
|
32
|
-
Platform: fakePlatform,
|
|
33
|
-
UnavailabilityError: class UnavailabilityError extends Error {
|
|
34
|
-
constructor(moduleName: string, propertyName: string) {
|
|
35
|
-
super(`${propertyName} is not available on ${moduleName}`);
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
const appStateListeners = new Set<(state: string) => void>();
|
|
41
|
-
const urlListeners = new Set<(event: { url: string }) => void>();
|
|
42
|
-
|
|
43
|
-
// Same reason as the expo-modules-core fake above: react-native's own source can't be parsed here.
|
|
44
|
-
// AppState and Linking are faked as real, drivable event sources because the Android auth-session
|
|
45
|
-
// polyfill's whole behavior is the race between them.
|
|
46
|
-
vi.mock('react-native', () => ({
|
|
47
|
-
AppState: {
|
|
48
|
-
currentState: 'active',
|
|
49
|
-
addEventListener: (_type: string, handler: (state: string) => void) => {
|
|
50
|
-
appStateListeners.add(handler);
|
|
51
|
-
return { remove: () => appStateListeners.delete(handler) };
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
Linking: {
|
|
55
|
-
addEventListener: (_type: string, handler: (event: { url: string }) => void) => {
|
|
56
|
-
urlListeners.add(handler);
|
|
57
|
-
return { remove: () => urlListeners.delete(handler) };
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
processColor: (value?: string) => (value === undefined ? undefined : `processed:${value}`),
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
|
-
const {
|
|
64
|
-
getCustomTabsSupportingBrowsersAsync,
|
|
65
|
-
warmUpAsync,
|
|
66
|
-
mayInitWithUrlAsync,
|
|
67
|
-
coolDownAsync,
|
|
68
|
-
openBrowserAsync,
|
|
69
|
-
dismissBrowser,
|
|
70
|
-
openAuthSessionAsync,
|
|
71
|
-
dismissAuthSession,
|
|
72
|
-
} = await import('./web-browser');
|
|
73
|
-
|
|
74
|
-
function emitAppState(state: string): void {
|
|
75
|
-
for (const listener of [...appStateListeners]) listener(state);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function emitUrl(url: string): void {
|
|
79
|
-
for (const listener of [...urlListeners]) listener({ url });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Lets every already-queued microtask and the polyfill's own promise chain settle.
|
|
83
|
-
function flush(): Promise<void> {
|
|
84
|
-
return new Promise(resolve => setTimeout(resolve, 0));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
afterEach(() => {
|
|
88
|
-
fakePlatform.OS = 'ios';
|
|
89
|
-
vi.clearAllMocks();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('openBrowserAsync', () => {
|
|
93
|
-
it('passes the url and options through to the native module', async () => {
|
|
94
|
-
await expect(openBrowserAsync('https://example.com', { showTitle: true })).resolves.toEqual({
|
|
95
|
-
type: 'opened',
|
|
96
|
-
});
|
|
97
|
-
expect(FAKE_NATIVE_WEB_BROWSER.openBrowserAsync).toHaveBeenCalledWith('https://example.com', {
|
|
98
|
-
showTitle: true,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// A color reaches native as a platform integer, never as the CSS string the caller wrote.
|
|
103
|
-
it('runs the three color options through processColor', async () => {
|
|
104
|
-
await openBrowserAsync('https://example.com', {
|
|
105
|
-
toolbarColor: '#ffffff',
|
|
106
|
-
secondaryToolbarColor: 'blue',
|
|
107
|
-
controlsColor: 'red',
|
|
108
|
-
});
|
|
109
|
-
expect(FAKE_NATIVE_WEB_BROWSER.openBrowserAsync).toHaveBeenCalledWith('https://example.com', {
|
|
110
|
-
toolbarColor: 'processed:#ffffff',
|
|
111
|
-
secondaryToolbarColor: 'processed:blue',
|
|
112
|
-
controlsColor: 'processed:red',
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('throws an UnavailabilityError-shaped error when the native method is absent', async () => {
|
|
117
|
-
const { openBrowserAsync: native } = FAKE_NATIVE_WEB_BROWSER;
|
|
118
|
-
// @ts-expect-error -- simulating a platform where the native module has no such method
|
|
119
|
-
FAKE_NATIVE_WEB_BROWSER.openBrowserAsync = undefined;
|
|
120
|
-
|
|
121
|
-
await expect(openBrowserAsync('https://example.com')).rejects.toThrow(
|
|
122
|
-
'openBrowserAsync is not available on expo-web-browser',
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
FAKE_NATIVE_WEB_BROWSER.openBrowserAsync = native;
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('dismissBrowser', () => {
|
|
130
|
-
it('delegates to the native module', async () => {
|
|
131
|
-
await expect(dismissBrowser()).resolves.toEqual({ type: 'dismiss' });
|
|
132
|
-
expect(FAKE_NATIVE_WEB_BROWSER.dismissBrowser).toHaveBeenCalled();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Android's Custom Tabs offer no way to close a tab programmatically, so the native module
|
|
136
|
-
// registers no dismissBrowser there at all.
|
|
137
|
-
it('throws where dismissing is not available', () => {
|
|
138
|
-
const { dismissBrowser: native } = FAKE_NATIVE_WEB_BROWSER;
|
|
139
|
-
// @ts-expect-error -- simulating Android, where the native module has no such method
|
|
140
|
-
FAKE_NATIVE_WEB_BROWSER.dismissBrowser = undefined;
|
|
141
|
-
|
|
142
|
-
expect(() => dismissBrowser()).toThrow('dismissBrowser is not available on expo-web-browser');
|
|
143
|
-
|
|
144
|
-
FAKE_NATIVE_WEB_BROWSER.dismissBrowser = native;
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe('the Custom Tabs service functions', () => {
|
|
149
|
-
it('delegate to the native module on Android', async () => {
|
|
150
|
-
fakePlatform.OS = 'android';
|
|
151
|
-
|
|
152
|
-
await expect(warmUpAsync('com.android.chrome')).resolves.toEqual({
|
|
153
|
-
servicePackage: 'com.android.chrome',
|
|
154
|
-
});
|
|
155
|
-
expect(FAKE_NATIVE_WEB_BROWSER.warmUpAsync).toHaveBeenCalledWith('com.android.chrome');
|
|
156
|
-
|
|
157
|
-
await coolDownAsync('com.android.chrome');
|
|
158
|
-
expect(FAKE_NATIVE_WEB_BROWSER.coolDownAsync).toHaveBeenCalledWith('com.android.chrome');
|
|
159
|
-
|
|
160
|
-
await mayInitWithUrlAsync('https://example.com', 'com.android.chrome');
|
|
161
|
-
expect(FAKE_NATIVE_WEB_BROWSER.mayInitWithUrlAsync).toHaveBeenCalledWith(
|
|
162
|
-
'https://example.com',
|
|
163
|
-
'com.android.chrome',
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
await expect(getCustomTabsSupportingBrowsersAsync()).resolves.toMatchObject({
|
|
167
|
-
browserPackages: ['com.android.chrome'],
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('resolve empty off Android without touching the native module', async () => {
|
|
172
|
-
await expect(warmUpAsync()).resolves.toEqual({});
|
|
173
|
-
await expect(coolDownAsync()).resolves.toEqual({});
|
|
174
|
-
await expect(mayInitWithUrlAsync('https://example.com')).resolves.toEqual({});
|
|
175
|
-
expect(FAKE_NATIVE_WEB_BROWSER.warmUpAsync).not.toHaveBeenCalled();
|
|
176
|
-
expect(FAKE_NATIVE_WEB_BROWSER.coolDownAsync).not.toHaveBeenCalled();
|
|
177
|
-
expect(FAKE_NATIVE_WEB_BROWSER.mayInitWithUrlAsync).not.toHaveBeenCalled();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('report the browser list as unavailable when the native module has no such method', async () => {
|
|
181
|
-
fakePlatform.OS = 'android';
|
|
182
|
-
const { getCustomTabsSupportingBrowsersAsync: native } = FAKE_NATIVE_WEB_BROWSER;
|
|
183
|
-
// @ts-expect-error -- simulating iOS, which registers the stub without the `Async` suffix
|
|
184
|
-
FAKE_NATIVE_WEB_BROWSER.getCustomTabsSupportingBrowsersAsync = undefined;
|
|
185
|
-
|
|
186
|
-
await expect(getCustomTabsSupportingBrowsersAsync()).rejects.toThrow(
|
|
187
|
-
'getCustomTabsSupportingBrowsersAsync is not available on expo-web-browser',
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
FAKE_NATIVE_WEB_BROWSER.getCustomTabsSupportingBrowsersAsync = native;
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
describe('openAuthSessionAsync on a platform with a native auth session', () => {
|
|
195
|
-
it('hands the url, redirect url and processed options to the native module', async () => {
|
|
196
|
-
await expect(
|
|
197
|
-
openAuthSessionAsync('https://login.example', 'myapp://callback', {
|
|
198
|
-
preferEphemeralSession: true,
|
|
199
|
-
controlsColor: 'red',
|
|
200
|
-
}),
|
|
201
|
-
).resolves.toEqual({ type: 'success', url: 'myapp://callback' });
|
|
202
|
-
|
|
203
|
-
expect(FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync).toHaveBeenCalledWith(
|
|
204
|
-
'https://login.example',
|
|
205
|
-
'myapp://callback',
|
|
206
|
-
{ preferEphemeralSession: true, controlsColor: 'processed:red' },
|
|
207
|
-
);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('throws an UnavailabilityError-shaped error when the native method is absent', async () => {
|
|
211
|
-
const { openAuthSessionAsync: native } = FAKE_NATIVE_WEB_BROWSER;
|
|
212
|
-
// @ts-expect-error -- simulating a platform where the native module has no such method
|
|
213
|
-
FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync = undefined;
|
|
214
|
-
|
|
215
|
-
await expect(openAuthSessionAsync('https://login.example')).rejects.toThrow(
|
|
216
|
-
'openAuthSessionAsync is not available on expo-web-browser',
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync = native;
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Android has no native auth session: a Custom Tab is opened and the result is whichever comes
|
|
224
|
-
// first, the deep link back into the app or the app returning to the foreground.
|
|
225
|
-
describe('openAuthSessionAsync on Android', () => {
|
|
226
|
-
it('resolves with the redirect once the deep link arrives', async () => {
|
|
227
|
-
fakePlatform.OS = 'android';
|
|
228
|
-
// Android's native module has neither of these; without removing them the polyfill would never
|
|
229
|
-
// be reached and the `finally` would dismiss a browser that cannot be dismissed.
|
|
230
|
-
const { openAuthSessionAsync: nativeAuth, dismissBrowser: nativeDismiss } =
|
|
231
|
-
FAKE_NATIVE_WEB_BROWSER;
|
|
232
|
-
// @ts-expect-error -- simulating Android's native surface
|
|
233
|
-
FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync = undefined;
|
|
234
|
-
// @ts-expect-error -- simulating Android's native surface
|
|
235
|
-
FAKE_NATIVE_WEB_BROWSER.dismissBrowser = undefined;
|
|
236
|
-
|
|
237
|
-
const session = openAuthSessionAsync('https://login.example', 'myapp://callback');
|
|
238
|
-
await flush();
|
|
239
|
-
emitUrl('myapp://callback?code=abc');
|
|
240
|
-
|
|
241
|
-
await expect(session).resolves.toEqual({
|
|
242
|
-
type: 'success',
|
|
243
|
-
url: 'myapp://callback?code=abc',
|
|
244
|
-
});
|
|
245
|
-
expect(FAKE_NATIVE_WEB_BROWSER.openBrowserAsync).toHaveBeenCalledWith(
|
|
246
|
-
'https://login.example',
|
|
247
|
-
{},
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// Release the browser-wait promise the race left pending, so the next session can start.
|
|
251
|
-
emitAppState('active');
|
|
252
|
-
await flush();
|
|
253
|
-
|
|
254
|
-
FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync = nativeAuth;
|
|
255
|
-
FAKE_NATIVE_WEB_BROWSER.dismissBrowser = nativeDismiss;
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('resolves as dismissed when the app returns to the foreground without a redirect', async () => {
|
|
259
|
-
fakePlatform.OS = 'android';
|
|
260
|
-
const { openAuthSessionAsync: nativeAuth, dismissBrowser: nativeDismiss } =
|
|
261
|
-
FAKE_NATIVE_WEB_BROWSER;
|
|
262
|
-
// @ts-expect-error -- simulating Android's native surface
|
|
263
|
-
FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync = undefined;
|
|
264
|
-
// @ts-expect-error -- simulating Android's native surface
|
|
265
|
-
FAKE_NATIVE_WEB_BROWSER.dismissBrowser = undefined;
|
|
266
|
-
|
|
267
|
-
const session = openAuthSessionAsync('https://login.example', 'myapp://callback');
|
|
268
|
-
await flush();
|
|
269
|
-
emitAppState('active');
|
|
270
|
-
|
|
271
|
-
await expect(session).resolves.toEqual({ type: 'dismiss' });
|
|
272
|
-
// The redirect listener is torn down again, so a second session is allowed to start.
|
|
273
|
-
expect(urlListeners.size).toBe(0);
|
|
274
|
-
|
|
275
|
-
FAKE_NATIVE_WEB_BROWSER.openAuthSessionAsync = nativeAuth;
|
|
276
|
-
FAKE_NATIVE_WEB_BROWSER.dismissBrowser = nativeDismiss;
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe('dismissAuthSession', () => {
|
|
281
|
-
it('dismisses the native auth session where one exists', () => {
|
|
282
|
-
dismissAuthSession();
|
|
283
|
-
expect(FAKE_NATIVE_WEB_BROWSER.dismissAuthSession).toHaveBeenCalled();
|
|
284
|
-
expect(FAKE_NATIVE_WEB_BROWSER.dismissBrowser).not.toHaveBeenCalled();
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('falls back to dismissing the browser where there is no native auth session', () => {
|
|
288
|
-
fakePlatform.OS = 'android';
|
|
289
|
-
|
|
290
|
-
dismissAuthSession();
|
|
291
|
-
|
|
292
|
-
expect(FAKE_NATIVE_WEB_BROWSER.dismissBrowser).toHaveBeenCalled();
|
|
293
|
-
expect(FAKE_NATIVE_WEB_BROWSER.dismissAuthSession).not.toHaveBeenCalled();
|
|
294
|
-
});
|
|
295
|
-
});
|