@rn-bridge-tools/expo 0.0.7 → 0.0.9

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 (66) hide show
  1. package/dist/WebViewBridge.d.ts +22 -0
  2. package/dist/WebViewBridge.d.ts.map +1 -0
  3. package/dist/__tests__/createHandlers.test.d.ts +2 -0
  4. package/dist/__tests__/createHandlers.test.d.ts.map +1 -0
  5. package/dist/createHandlers.d.ts +5 -0
  6. package/dist/createHandlers.d.ts.map +1 -0
  7. package/dist/handlers/auth.d.ts +6 -0
  8. package/dist/handlers/auth.d.ts.map +1 -0
  9. package/dist/handlers/browser.d.ts +6 -0
  10. package/dist/handlers/browser.d.ts.map +1 -0
  11. package/dist/handlers/camera.d.ts +6 -0
  12. package/dist/handlers/camera.d.ts.map +1 -0
  13. package/dist/handlers/clipboard.d.ts +6 -0
  14. package/dist/handlers/clipboard.d.ts.map +1 -0
  15. package/dist/handlers/device.d.ts +7 -0
  16. package/dist/handlers/device.d.ts.map +1 -0
  17. package/dist/handlers/file.d.ts +8 -0
  18. package/dist/handlers/file.d.ts.map +1 -0
  19. package/dist/handlers/haptic.d.ts +7 -0
  20. package/dist/handlers/haptic.d.ts.map +1 -0
  21. package/dist/handlers/iap.d.ts +7 -0
  22. package/dist/handlers/iap.d.ts.map +1 -0
  23. package/dist/handlers/keyboard.d.ts +6 -0
  24. package/dist/handlers/keyboard.d.ts.map +1 -0
  25. package/dist/handlers/location.d.ts +7 -0
  26. package/dist/handlers/location.d.ts.map +1 -0
  27. package/dist/handlers/navigation.d.ts +8 -0
  28. package/dist/handlers/navigation.d.ts.map +1 -0
  29. package/dist/handlers/permission.d.ts +7 -0
  30. package/dist/handlers/permission.d.ts.map +1 -0
  31. package/dist/handlers/preference.d.ts +8 -0
  32. package/dist/handlers/preference.d.ts.map +1 -0
  33. package/dist/handlers/push.d.ts +6 -0
  34. package/dist/handlers/push.d.ts.map +1 -0
  35. package/dist/handlers/scanner.d.ts +5 -0
  36. package/dist/handlers/scanner.d.ts.map +1 -0
  37. package/dist/handlers/share.d.ts +5 -0
  38. package/dist/handlers/share.d.ts.map +1 -0
  39. package/dist/handlers/statusbar.d.ts +7 -0
  40. package/dist/handlers/statusbar.d.ts.map +1 -0
  41. package/dist/index.d.ts +22 -124
  42. package/dist/index.d.ts.map +1 -0
  43. package/package.json +7 -6
  44. package/src/WebViewBridge.tsx +58 -0
  45. package/src/__tests__/createHandlers.test.ts +88 -0
  46. package/src/createHandlers.ts +67 -0
  47. package/src/handlers/auth.ts +69 -0
  48. package/src/handlers/browser.ts +42 -0
  49. package/src/handlers/camera.ts +97 -0
  50. package/src/handlers/clipboard.ts +32 -0
  51. package/src/handlers/device.ts +71 -0
  52. package/src/handlers/file.ts +126 -0
  53. package/src/handlers/haptic.ts +56 -0
  54. package/src/handlers/iap.ts +23 -0
  55. package/src/handlers/keyboard.ts +20 -0
  56. package/src/handlers/location.ts +104 -0
  57. package/src/handlers/navigation.ts +29 -0
  58. package/src/handlers/permission.ts +107 -0
  59. package/src/handlers/preference.ts +58 -0
  60. package/src/handlers/push.ts +47 -0
  61. package/src/handlers/scanner.ts +31 -0
  62. package/src/handlers/share.ts +31 -0
  63. package/src/handlers/statusbar.ts +35 -0
  64. package/src/index.ts +23 -0
  65. package/dist/index.js +0 -846
  66. package/dist/index.js.map +0 -1
@@ -0,0 +1,20 @@
1
+ import type { KeyboardNamespace } from '@rn-bridge-tools/core';
2
+ import { Keyboard } from 'react-native';
3
+
4
+ export const keyboardHandlers = {
5
+ 'keyboard.dismiss': async (
6
+ _payload: KeyboardNamespace['keyboard.dismiss']['request'],
7
+ ): Promise<KeyboardNamespace['keyboard.dismiss']['response']> => {
8
+ Keyboard.dismiss();
9
+ return { success: true };
10
+ },
11
+
12
+ 'keyboard.getState': async (
13
+ _payload: KeyboardNamespace['keyboard.getState']['request'],
14
+ ): Promise<KeyboardNamespace['keyboard.getState']['response']> => {
15
+ return {
16
+ visible: false,
17
+ height: 0,
18
+ };
19
+ },
20
+ };
@@ -0,0 +1,104 @@
1
+ import type { LocationNamespace } from '@rn-bridge-tools/core';
2
+
3
+ declare const require: (id: string) => unknown;
4
+
5
+ interface ExpoLocationModule {
6
+ Accuracy: {
7
+ Lowest: number;
8
+ Low: number;
9
+ Balanced: number;
10
+ High: number;
11
+ Highest: number;
12
+ };
13
+ getCurrentPositionAsync: (opts: {
14
+ accuracy?: number;
15
+ }) => Promise<{
16
+ coords: {
17
+ latitude: number;
18
+ longitude: number;
19
+ altitude: number | null;
20
+ accuracy: number | null;
21
+ };
22
+ timestamp: number;
23
+ }>;
24
+ watchPositionAsync: (
25
+ opts: { accuracy?: number; distanceInterval?: number; timeInterval?: number },
26
+ callback: (location: unknown) => void,
27
+ ) => Promise<{ remove: () => void }>;
28
+ }
29
+
30
+ const watchSubscriptions = new Map<string, { remove: () => void }>();
31
+ let watchCounter = 0;
32
+
33
+ export const locationHandlers = {
34
+ 'location.getCurrent': async (
35
+ payload: LocationNamespace['location.getCurrent']['request'],
36
+ ): Promise<LocationNamespace['location.getCurrent']['response']> => {
37
+ let Location: ExpoLocationModule | null = null;
38
+ try { Location = require('expo-location') as ExpoLocationModule; } catch {}
39
+ if (!Location) return { success: false, error: 'MODULE_NOT_INSTALLED: expo-location' } as never;
40
+
41
+ const accuracyMap: Record<string, number> = {
42
+ lowest: Location.Accuracy.Lowest,
43
+ low: Location.Accuracy.Low,
44
+ balanced: Location.Accuracy.Balanced,
45
+ high: Location.Accuracy.High,
46
+ highest: Location.Accuracy.Highest,
47
+ };
48
+
49
+ const result = await Location.getCurrentPositionAsync({
50
+ accuracy: accuracyMap[payload.accuracy ?? 'balanced'] ?? Location.Accuracy.Balanced,
51
+ });
52
+
53
+ return {
54
+ lat: result.coords.latitude,
55
+ lng: result.coords.longitude,
56
+ altitude: result.coords.altitude ?? undefined,
57
+ accuracy: result.coords.accuracy ?? undefined,
58
+ timestamp: result.timestamp,
59
+ };
60
+ },
61
+
62
+ 'location.watchStart': async (
63
+ payload: LocationNamespace['location.watchStart']['request'],
64
+ ): Promise<LocationNamespace['location.watchStart']['response']> => {
65
+ let Location: ExpoLocationModule | null = null;
66
+ try { Location = require('expo-location') as ExpoLocationModule; } catch {}
67
+ if (!Location) return { success: false, error: 'MODULE_NOT_INSTALLED: expo-location' } as never;
68
+
69
+ watchCounter += 1;
70
+ const watchId = `watch_${watchCounter}`;
71
+
72
+ const accuracyMap: Record<string, number> = {
73
+ low: Location.Accuracy.Low,
74
+ balanced: Location.Accuracy.Balanced,
75
+ high: Location.Accuracy.High,
76
+ };
77
+
78
+ const subscription = await Location.watchPositionAsync(
79
+ {
80
+ accuracy: accuracyMap[payload.accuracy ?? 'balanced'] ?? Location.Accuracy.Balanced,
81
+ distanceInterval: payload.distanceInterval,
82
+ timeInterval: payload.timeInterval,
83
+ },
84
+ (_location) => {
85
+ // Event push is handled by the WebViewBridge component via emit
86
+ },
87
+ );
88
+
89
+ watchSubscriptions.set(watchId, subscription);
90
+ return { watchId };
91
+ },
92
+
93
+ 'location.watchStop': async (
94
+ payload: LocationNamespace['location.watchStop']['request'],
95
+ ): Promise<LocationNamespace['location.watchStop']['response']> => {
96
+ const subscription = watchSubscriptions.get(payload.watchId);
97
+ if (subscription) {
98
+ subscription.remove();
99
+ watchSubscriptions.delete(payload.watchId);
100
+ return { success: true };
101
+ }
102
+ return { success: false };
103
+ },
104
+ };
@@ -0,0 +1,29 @@
1
+ import type { NavigationNamespace } from '@rn-bridge-tools/core';
2
+
3
+ export const navigationHandlers = {
4
+ 'navigation.goBack': async (
5
+ _payload: NavigationNamespace['navigation.goBack']['request'],
6
+ ): Promise<NavigationNamespace['navigation.goBack']['response']> => {
7
+ // WebView goBack is handled by the WebViewBridge component
8
+ return { success: true };
9
+ },
10
+
11
+ 'navigation.push': async (
12
+ _payload: NavigationNamespace['navigation.push']['request'],
13
+ ): Promise<NavigationNamespace['navigation.push']['response']> => {
14
+ // Navigation push requires access to the navigation context
15
+ return { success: true };
16
+ },
17
+
18
+ 'navigation.close': async (
19
+ _payload: NavigationNamespace['navigation.close']['request'],
20
+ ): Promise<NavigationNamespace['navigation.close']['response']> => {
21
+ return { success: true };
22
+ },
23
+
24
+ 'navigation.setSwipeBack': async (
25
+ _payload: NavigationNamespace['navigation.setSwipeBack']['request'],
26
+ ): Promise<NavigationNamespace['navigation.setSwipeBack']['response']> => {
27
+ return { success: true };
28
+ },
29
+ };
@@ -0,0 +1,107 @@
1
+ import type { PermissionNamespace, PermissionType } from '@rn-bridge-tools/core';
2
+ import { Linking } from 'react-native';
3
+
4
+ declare const require: (id: string) => unknown;
5
+
6
+ interface PermissionCameraModule {
7
+ Camera: {
8
+ getCameraPermissionsAsync: () => Promise<{ status: string; canAskAgain?: boolean }>;
9
+ requestCameraPermissionsAsync: () => Promise<{ status: string; canAskAgain?: boolean }>;
10
+ };
11
+ }
12
+
13
+ interface PermissionLocationModule {
14
+ getForegroundPermissionsAsync: () => Promise<{ status: string; canAskAgain?: boolean }>;
15
+ requestForegroundPermissionsAsync: () => Promise<{ status: string; canAskAgain?: boolean }>;
16
+ }
17
+
18
+ interface PermissionNotificationsModule {
19
+ getPermissionsAsync: () => Promise<{ status: string; canAskAgain?: boolean }>;
20
+ requestPermissionsAsync: () => Promise<{ status: string; canAskAgain?: boolean }>;
21
+ }
22
+
23
+ function getPermissionModule(permission: PermissionType) {
24
+ switch (permission) {
25
+ case 'camera': {
26
+ let mod: PermissionCameraModule | null = null;
27
+ try { mod = require('expo-camera') as PermissionCameraModule; } catch {}
28
+ return mod
29
+ ? {
30
+ check: () => mod.Camera.getCameraPermissionsAsync(),
31
+ request: () => mod.Camera.requestCameraPermissionsAsync(),
32
+ }
33
+ : null;
34
+ }
35
+ case 'location': {
36
+ let mod: PermissionLocationModule | null = null;
37
+ try { mod = require('expo-location') as PermissionLocationModule; } catch {}
38
+ return mod
39
+ ? {
40
+ check: () => mod.getForegroundPermissionsAsync(),
41
+ request: () => mod.requestForegroundPermissionsAsync(),
42
+ }
43
+ : null;
44
+ }
45
+ case 'notifications': {
46
+ let mod: PermissionNotificationsModule | null = null;
47
+ try { mod = require('expo-notifications') as PermissionNotificationsModule; } catch {}
48
+ return mod
49
+ ? {
50
+ check: () => mod.getPermissionsAsync(),
51
+ request: () => mod.requestPermissionsAsync(),
52
+ }
53
+ : null;
54
+ }
55
+ default:
56
+ return null;
57
+ }
58
+ }
59
+
60
+ export const permissionHandlers = {
61
+ 'permission.check': async (
62
+ payload: PermissionNamespace['permission.check']['request'],
63
+ ): Promise<PermissionNamespace['permission.check']['response']> => {
64
+ const mod = getPermissionModule(payload.permission);
65
+ if (!mod) {
66
+ return { status: 'undetermined', canAskAgain: true };
67
+ }
68
+ try {
69
+ const result = await mod.check();
70
+ return {
71
+ status: result.status as 'granted' | 'denied' | 'undetermined',
72
+ canAskAgain: result.canAskAgain ?? true,
73
+ };
74
+ } catch {
75
+ return { status: 'undetermined', canAskAgain: true };
76
+ }
77
+ },
78
+
79
+ 'permission.request': async (
80
+ payload: PermissionNamespace['permission.request']['request'],
81
+ ): Promise<PermissionNamespace['permission.request']['response']> => {
82
+ const mod = getPermissionModule(payload.permission);
83
+ if (!mod) {
84
+ return { status: 'undetermined', canAskAgain: true };
85
+ }
86
+ try {
87
+ const result = await mod.request();
88
+ return {
89
+ status: result.status as 'granted' | 'denied' | 'undetermined',
90
+ canAskAgain: result.canAskAgain ?? true,
91
+ };
92
+ } catch {
93
+ return { status: 'undetermined', canAskAgain: true };
94
+ }
95
+ },
96
+
97
+ 'permission.openSettings': async (
98
+ _payload: PermissionNamespace['permission.openSettings']['request'],
99
+ ): Promise<PermissionNamespace['permission.openSettings']['response']> => {
100
+ try {
101
+ await Linking.openSettings();
102
+ return { success: true };
103
+ } catch {
104
+ return { success: false };
105
+ }
106
+ },
107
+ };
@@ -0,0 +1,58 @@
1
+ import type { PreferenceNamespace } from '@rn-bridge-tools/core';
2
+
3
+ declare const require: (id: string) => unknown;
4
+
5
+ type AsyncStorage = {
6
+ default: {
7
+ getItem: (key: string) => Promise<string | null>;
8
+ setItem: (key: string, value: string) => Promise<void>;
9
+ removeItem: (key: string) => Promise<void>;
10
+ clear: () => Promise<void>;
11
+ };
12
+ };
13
+
14
+ export const preferenceHandlers = {
15
+ 'preference.get': async (
16
+ payload: PreferenceNamespace['preference.get']['request'],
17
+ ): Promise<PreferenceNamespace['preference.get']['response']> => {
18
+ let mod: AsyncStorage | null = null;
19
+ try { mod = require('@react-native-async-storage/async-storage') as AsyncStorage; } catch {}
20
+ if (!mod) return { value: null };
21
+
22
+ const value = await mod.default.getItem(payload.key);
23
+ return { value };
24
+ },
25
+
26
+ 'preference.set': async (
27
+ payload: PreferenceNamespace['preference.set']['request'],
28
+ ): Promise<PreferenceNamespace['preference.set']['response']> => {
29
+ let mod: AsyncStorage | null = null;
30
+ try { mod = require('@react-native-async-storage/async-storage') as AsyncStorage; } catch {}
31
+ if (!mod) return { success: false };
32
+
33
+ await mod.default.setItem(payload.key, payload.value);
34
+ return { success: true };
35
+ },
36
+
37
+ 'preference.remove': async (
38
+ payload: PreferenceNamespace['preference.remove']['request'],
39
+ ): Promise<PreferenceNamespace['preference.remove']['response']> => {
40
+ let mod: AsyncStorage | null = null;
41
+ try { mod = require('@react-native-async-storage/async-storage') as AsyncStorage; } catch {}
42
+ if (!mod) return { success: false };
43
+
44
+ await mod.default.removeItem(payload.key);
45
+ return { success: true };
46
+ },
47
+
48
+ 'preference.clear': async (
49
+ _payload: PreferenceNamespace['preference.clear']['request'],
50
+ ): Promise<PreferenceNamespace['preference.clear']['response']> => {
51
+ let mod: AsyncStorage | null = null;
52
+ try { mod = require('@react-native-async-storage/async-storage') as AsyncStorage; } catch {}
53
+ if (!mod) return { success: false };
54
+
55
+ await mod.default.clear();
56
+ return { success: true };
57
+ },
58
+ };
@@ -0,0 +1,47 @@
1
+ import type { PushNamespace } from '@rn-bridge-tools/core';
2
+ import { Platform } from 'react-native';
3
+
4
+ declare const require: (id: string) => unknown;
5
+
6
+ interface NotificationsModule {
7
+ getExpoPushTokenAsync: () => Promise<{ data: string }>;
8
+ requestPermissionsAsync: () => Promise<{ status: string }>;
9
+ }
10
+
11
+ export const pushHandlers = {
12
+ 'push.getToken': async (
13
+ _payload: PushNamespace['push.getToken']['request'],
14
+ ): Promise<PushNamespace['push.getToken']['response']> => {
15
+ let Notifications: NotificationsModule | null = null;
16
+ try { Notifications = require('expo-notifications') as NotificationsModule; } catch {}
17
+ if (!Notifications) return { success: false, error: 'MODULE_NOT_INSTALLED: expo-notifications' } as never;
18
+
19
+ try {
20
+ const token = await Notifications.getExpoPushTokenAsync();
21
+ return {
22
+ token: token.data,
23
+ platform: Platform.OS === 'ios' ? 'apns' : 'fcm',
24
+ };
25
+ } catch {
26
+ return { token: '', platform: Platform.OS === 'ios' ? 'apns' : 'fcm' };
27
+ }
28
+ },
29
+
30
+ 'push.requestPermission': async (
31
+ _payload: PushNamespace['push.requestPermission']['request'],
32
+ ): Promise<PushNamespace['push.requestPermission']['response']> => {
33
+ let Notifications: NotificationsModule | null = null;
34
+ try { Notifications = require('expo-notifications') as NotificationsModule; } catch {}
35
+ if (!Notifications) return { success: false, error: 'MODULE_NOT_INSTALLED: expo-notifications' } as never;
36
+
37
+ try {
38
+ const { status } = await Notifications.requestPermissionsAsync();
39
+ return {
40
+ granted: status === 'granted',
41
+ status: status as 'granted' | 'denied' | 'undetermined',
42
+ };
43
+ } catch {
44
+ return { granted: false, status: 'denied' };
45
+ }
46
+ },
47
+ };
@@ -0,0 +1,31 @@
1
+ import type { ScannerNamespace } from '@rn-bridge-tools/core';
2
+
3
+ declare const require: (id: string) => unknown;
4
+
5
+ interface CameraModule {
6
+ Camera: {
7
+ requestCameraPermissionsAsync: () => Promise<{ status: string }>;
8
+ };
9
+ }
10
+
11
+ export const scannerHandlers = {
12
+ 'scanner.scanQR': async (
13
+ _payload: ScannerNamespace['scanner.scanQR']['request'],
14
+ ): Promise<ScannerNamespace['scanner.scanQR']['response']> => {
15
+ let Camera: CameraModule | null = null;
16
+ try { Camera = require('expo-camera') as CameraModule; } catch {}
17
+ if (!Camera) return { success: false, error: 'MODULE_NOT_INSTALLED: expo-camera' } as never;
18
+
19
+ try {
20
+ const { status } = await Camera.Camera.requestCameraPermissionsAsync();
21
+ if (status !== 'granted') {
22
+ return { success: false, cancelled: true };
23
+ }
24
+ // QR scanning is typically handled via a component, not a direct call.
25
+ // Return a placeholder indicating the scanner should be opened.
26
+ return { success: false, error: 'Use Camera component for QR scanning' } as never;
27
+ } catch {
28
+ return { success: false };
29
+ }
30
+ },
31
+ };
@@ -0,0 +1,31 @@
1
+ import type { ShareNamespace } from '@rn-bridge-tools/core';
2
+
3
+ declare const require: (id: string) => unknown;
4
+
5
+ interface SharingModule {
6
+ isAvailableAsync: () => Promise<boolean>;
7
+ shareAsync: (url: string, opts?: { dialogTitle?: string }) => Promise<void>;
8
+ }
9
+
10
+ export const shareHandlers = {
11
+ 'share.open': async (
12
+ payload: ShareNamespace['share.open']['request'],
13
+ ): Promise<ShareNamespace['share.open']['response']> => {
14
+ let Sharing: SharingModule | null = null;
15
+ try { Sharing = require('expo-sharing') as SharingModule; } catch {}
16
+ if (!Sharing) return { success: false, error: 'MODULE_NOT_INSTALLED: expo-sharing' } as never;
17
+
18
+ try {
19
+ const isAvailable = await Sharing.isAvailableAsync();
20
+ if (!isAvailable) {
21
+ return { success: false };
22
+ }
23
+ await Sharing.shareAsync(payload.url ?? '', {
24
+ dialogTitle: payload.title,
25
+ });
26
+ return { success: true };
27
+ } catch {
28
+ return { success: false };
29
+ }
30
+ },
31
+ };
@@ -0,0 +1,35 @@
1
+ import type { StatusBarNamespace } from '@rn-bridge-tools/core';
2
+ import { StatusBar } from 'react-native';
3
+
4
+ export const statusbarHandlers = {
5
+ 'statusbar.setStyle': async (
6
+ payload: StatusBarNamespace['statusbar.setStyle']['request'],
7
+ ): Promise<StatusBarNamespace['statusbar.setStyle']['response']> => {
8
+ const styleMap: Record<string, 'default' | 'light-content' | 'dark-content'> = {
9
+ light: 'light-content',
10
+ dark: 'dark-content',
11
+ auto: 'default',
12
+ };
13
+ StatusBar.setBarStyle(styleMap[payload.style] ?? 'default');
14
+ return { success: true };
15
+ },
16
+
17
+ 'statusbar.setBackgroundColor': async (
18
+ payload: StatusBarNamespace['statusbar.setBackgroundColor']['request'],
19
+ ): Promise<StatusBarNamespace['statusbar.setBackgroundColor']['response']> => {
20
+ StatusBar.setBackgroundColor(payload.color, payload.animated ?? false);
21
+ return { success: true };
22
+ },
23
+
24
+ 'statusbar.setHidden': async (
25
+ payload: StatusBarNamespace['statusbar.setHidden']['request'],
26
+ ): Promise<StatusBarNamespace['statusbar.setHidden']['response']> => {
27
+ const animationMap: Record<string, 'fade' | 'slide' | 'none'> = {
28
+ fade: 'fade',
29
+ slide: 'slide',
30
+ none: 'none',
31
+ };
32
+ StatusBar.setHidden(payload.hidden, animationMap[payload.animation ?? 'none']);
33
+ return { success: true };
34
+ },
35
+ };
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ export { WebViewBridge, createBridgeWebView } from './WebViewBridge';
2
+ export type { WebViewBridgeProps, WebViewBridgeRef } from './WebViewBridge';
3
+ export { createDefaultHandlers } from './createHandlers';
4
+ export type { HandlerFn, HandlerMap } from './createHandlers';
5
+
6
+ // Re-export handler maps for advanced usage
7
+ export { cameraHandlers } from './handlers/camera';
8
+ export { locationHandlers } from './handlers/location';
9
+ export { fileHandlers } from './handlers/file';
10
+ export { shareHandlers } from './handlers/share';
11
+ export { deviceHandlers } from './handlers/device';
12
+ export { statusbarHandlers } from './handlers/statusbar';
13
+ export { keyboardHandlers } from './handlers/keyboard';
14
+ export { hapticHandlers } from './handlers/haptic';
15
+ export { clipboardHandlers } from './handlers/clipboard';
16
+ export { scannerHandlers } from './handlers/scanner';
17
+ export { authHandlers } from './handlers/auth';
18
+ export { iapHandlers } from './handlers/iap';
19
+ export { pushHandlers } from './handlers/push';
20
+ export { permissionHandlers } from './handlers/permission';
21
+ export { preferenceHandlers } from './handlers/preference';
22
+ export { navigationHandlers } from './handlers/navigation';
23
+ export { browserHandlers } from './handlers/browser';