@savers_app/react-native-sandbox-sdk 1.2.6 → 1.2.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.md +22 -151
- package/lib/module/core/runtime.js +14 -0
- package/lib/module/core/runtime.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/url/urlGenerator.js +20 -6
- package/lib/module/services/url/urlGenerator.js.map +1 -1
- package/lib/module/services/webview/DualWebViewBridgeController.js +267 -0
- package/lib/module/services/webview/DualWebViewBridgeController.js.map +1 -0
- package/lib/module/services/webview/dualWebViewBridge.types.js +19 -0
- package/lib/module/services/webview/dualWebViewBridge.types.js.map +1 -0
- package/lib/module/utils/config.js +2 -0
- package/lib/module/utils/config.js.map +1 -1
- package/lib/typescript/src/core/runtime.d.ts +4 -0
- package/lib/typescript/src/core/runtime.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/services/url/urlGenerator.d.ts +0 -1
- package/lib/typescript/src/services/url/urlGenerator.d.ts.map +1 -1
- package/lib/typescript/src/services/webview/DualWebViewBridgeController.d.ts +15 -0
- package/lib/typescript/src/services/webview/DualWebViewBridgeController.d.ts.map +1 -0
- package/lib/typescript/src/services/webview/dualWebViewBridge.types.d.ts +20 -0
- package/lib/typescript/src/services/webview/dualWebViewBridge.types.d.ts.map +1 -0
- package/lib/typescript/src/utils/config.d.ts.map +1 -1
- package/package.json +17 -9
- package/src/core/runtime.ts +21 -0
- package/src/index.tsx +2 -0
- package/src/services/url/urlGenerator.ts +18 -7
- package/src/services/webview/DualWebViewBridgeController.tsx +330 -0
- package/src/services/webview/dualWebViewBridge.types.ts +32 -0
- package/src/utils/config.ts +1 -0
- package/lib/module/services/device/location.js +0 -47
- package/lib/module/services/device/location.js.map +0 -1
- package/lib/typescript/src/services/device/location.d.ts +0 -8
- package/lib/typescript/src/services/device/location.d.ts.map +0 -1
- package/src/services/device/location.ts +0 -51
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import WebView from 'react-native-webview';
|
|
4
|
+
import {
|
|
5
|
+
type DualWebViewBridgeEnvelope,
|
|
6
|
+
parseDualWebViewEnvelope,
|
|
7
|
+
} from './dualWebViewBridge.types';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TRAVEL_PORTAL_URL = 'https://sandbox.travelercashback.com';
|
|
10
|
+
|
|
11
|
+
/** Must match `applicationNameForUserAgent` on clo-app WebViews (DualWebViewBridgeController). */
|
|
12
|
+
const WEBVIEW_UA_MARKER = 'CloAppWebView';
|
|
13
|
+
|
|
14
|
+
/** Legacy / Hub: open travel from first WebView without JSON envelope. */
|
|
15
|
+
const TRAVEL_DEEP_LINK = 'saversapp://travel';
|
|
16
|
+
const LEGACY_OPEN_TRAVEL_ACTIONS = ['OPEN_TRAVEL_PORTAL', 'OPEN_TRAVEL'];
|
|
17
|
+
|
|
18
|
+
export type Surface = 'savers' | 'travel';
|
|
19
|
+
|
|
20
|
+
export type DualWebViewBridgeControllerProps = {
|
|
21
|
+
saversAppUrl: string;
|
|
22
|
+
travelPortalUrl?: string;
|
|
23
|
+
partnerLogo?: string;
|
|
24
|
+
renderTravelBackIcon?: () => React.ReactNode;
|
|
25
|
+
initialSurface?: Surface;
|
|
26
|
+
onSaversSdkMessage?: (raw: string, postBack: (data: unknown) => void) => void;
|
|
27
|
+
onLoadEndSavers?: () => void;
|
|
28
|
+
onLoadEndTravel?: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function buildInjectNativeBridgeScript(
|
|
32
|
+
detail: Record<string, unknown>
|
|
33
|
+
): string {
|
|
34
|
+
return `
|
|
35
|
+
(function(){
|
|
36
|
+
var detail = ${JSON.stringify(detail)};
|
|
37
|
+
window.dispatchEvent(new CustomEvent('nativeBridge', { detail: detail }));
|
|
38
|
+
true;
|
|
39
|
+
})();
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DualWebViewBridgeController: React.FC<
|
|
44
|
+
DualWebViewBridgeControllerProps
|
|
45
|
+
> = ({
|
|
46
|
+
saversAppUrl,
|
|
47
|
+
travelPortalUrl = DEFAULT_TRAVEL_PORTAL_URL,
|
|
48
|
+
partnerLogo,
|
|
49
|
+
renderTravelBackIcon,
|
|
50
|
+
initialSurface = 'savers',
|
|
51
|
+
onSaversSdkMessage,
|
|
52
|
+
onLoadEndSavers,
|
|
53
|
+
onLoadEndTravel,
|
|
54
|
+
}) => {
|
|
55
|
+
const saversRef = useRef<WebView>(null);
|
|
56
|
+
const travelRef = useRef<WebView>(null);
|
|
57
|
+
|
|
58
|
+
const [surface, setSurface] = useState<Surface>(initialSurface);
|
|
59
|
+
const [saversKey, setSaversKey] = useState(0);
|
|
60
|
+
const [travelKey, setTravelKey] = useState(0);
|
|
61
|
+
|
|
62
|
+
const openTravel = useCallback(() => {
|
|
63
|
+
setSurface('travel');
|
|
64
|
+
setTravelKey((k) => k + 1);
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const backToSavers = useCallback(() => {
|
|
68
|
+
setSurface('savers');
|
|
69
|
+
setSaversKey((k) => k + 1);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const relayTo = useCallback(
|
|
73
|
+
(
|
|
74
|
+
target: 'savers' | 'travel',
|
|
75
|
+
payload: unknown,
|
|
76
|
+
from: 'savers' | 'travel'
|
|
77
|
+
) => {
|
|
78
|
+
const detail = {
|
|
79
|
+
bridge: 'dual-webview',
|
|
80
|
+
from,
|
|
81
|
+
action: 'relay' as const,
|
|
82
|
+
payload,
|
|
83
|
+
};
|
|
84
|
+
const ref = target === 'savers' ? saversRef : travelRef;
|
|
85
|
+
ref.current?.injectJavaScript(buildInjectNativeBridgeScript(detail));
|
|
86
|
+
},
|
|
87
|
+
[]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const handleBridgeEnvelope = useCallback(
|
|
91
|
+
(env: DualWebViewBridgeEnvelope): boolean => {
|
|
92
|
+
switch (env.action) {
|
|
93
|
+
case 'open_travel':
|
|
94
|
+
openTravel();
|
|
95
|
+
return true;
|
|
96
|
+
case 'close_travel':
|
|
97
|
+
backToSavers();
|
|
98
|
+
return true;
|
|
99
|
+
case 'relay':
|
|
100
|
+
if (env.to === 'savers') {
|
|
101
|
+
relayTo('savers', env.payload, env.from);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (env.to === 'travel') {
|
|
105
|
+
relayTo('travel', env.payload, env.from);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
default:
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[backToSavers, openTravel, relayTo]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const tryLegacyOpenTravel = useCallback(
|
|
117
|
+
(raw: string): boolean => {
|
|
118
|
+
if (!raw || typeof raw !== 'string') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const data = JSON.parse(raw);
|
|
123
|
+
if (
|
|
124
|
+
data?.action === 'OPEN_TRAVEL_PORTAL' ||
|
|
125
|
+
data?.type === 'OPEN_TRAVEL' ||
|
|
126
|
+
data?.openTravel === true
|
|
127
|
+
) {
|
|
128
|
+
openTravel();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
/* ignore */
|
|
133
|
+
}
|
|
134
|
+
if (LEGACY_OPEN_TRAVEL_ACTIONS.some((s) => raw.includes(s))) {
|
|
135
|
+
openTravel();
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
},
|
|
140
|
+
[openTravel]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const onMessageSavers = useCallback(
|
|
144
|
+
(e: { nativeEvent: { data: string } }) => {
|
|
145
|
+
const raw = e?.nativeEvent?.data ?? '';
|
|
146
|
+
const env = parseDualWebViewEnvelope(raw);
|
|
147
|
+
|
|
148
|
+
if (env && env.from === 'savers' && handleBridgeEnvelope(env)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (tryLegacyOpenTravel(raw)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const postBack = (data: unknown) => {
|
|
156
|
+
const detail = {
|
|
157
|
+
bridge: 'dual-webview',
|
|
158
|
+
from: 'native',
|
|
159
|
+
action: 'relay',
|
|
160
|
+
payload: data,
|
|
161
|
+
};
|
|
162
|
+
saversRef.current?.injectJavaScript(
|
|
163
|
+
buildInjectNativeBridgeScript(detail)
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
onSaversSdkMessage?.(raw, postBack);
|
|
167
|
+
},
|
|
168
|
+
[handleBridgeEnvelope, onSaversSdkMessage, tryLegacyOpenTravel]
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const onMessageTravel = useCallback(
|
|
172
|
+
(e: { nativeEvent: { data: string } }) => {
|
|
173
|
+
const raw = e?.nativeEvent?.data ?? '';
|
|
174
|
+
const env = parseDualWebViewEnvelope(raw);
|
|
175
|
+
|
|
176
|
+
if (env && env.from === 'travel' && handleBridgeEnvelope(env)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (tryLegacyOpenTravel(raw)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[handleBridgeEnvelope, tryLegacyOpenTravel]
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const onShouldStartLoadSavers = useCallback(
|
|
187
|
+
(request: { url: string }) => {
|
|
188
|
+
const { url } = request;
|
|
189
|
+
if (url.startsWith(TRAVEL_DEEP_LINK) || url === 'saversapp://travel/') {
|
|
190
|
+
openTravel();
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
},
|
|
195
|
+
[openTravel]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const travelSource = useMemo(
|
|
199
|
+
() => ({ uri: travelPortalUrl }),
|
|
200
|
+
[travelPortalUrl]
|
|
201
|
+
);
|
|
202
|
+
const saversSource = useMemo(() => ({ uri: saversAppUrl }), [saversAppUrl]);
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<View style={styles.root}>
|
|
206
|
+
{surface === 'savers' ? (
|
|
207
|
+
<WebView
|
|
208
|
+
key={saversKey}
|
|
209
|
+
ref={saversRef}
|
|
210
|
+
source={saversSource}
|
|
211
|
+
originWhitelist={['*']}
|
|
212
|
+
applicationNameForUserAgent={WEBVIEW_UA_MARKER}
|
|
213
|
+
onMessage={onMessageSavers}
|
|
214
|
+
onShouldStartLoadWithRequest={onShouldStartLoadSavers}
|
|
215
|
+
onLoadEnd={onLoadEndSavers}
|
|
216
|
+
style={styles.webview}
|
|
217
|
+
sharedCookiesEnabled
|
|
218
|
+
incognito={false}
|
|
219
|
+
webviewDebuggingEnabled={__DEV__}
|
|
220
|
+
/>
|
|
221
|
+
) : null}
|
|
222
|
+
|
|
223
|
+
{surface === 'travel' ? (
|
|
224
|
+
<View style={styles.travelColumn}>
|
|
225
|
+
<View style={styles.header}>
|
|
226
|
+
<TouchableOpacity
|
|
227
|
+
onPress={backToSavers}
|
|
228
|
+
hitSlop={16}
|
|
229
|
+
accessibilityRole="button"
|
|
230
|
+
accessibilityLabel="Back"
|
|
231
|
+
style={styles.backButton}
|
|
232
|
+
>
|
|
233
|
+
{renderTravelBackIcon ? (
|
|
234
|
+
renderTravelBackIcon()
|
|
235
|
+
) : (
|
|
236
|
+
<View style={styles.backIconWrapper}>
|
|
237
|
+
<View style={styles.backIconShaft} />
|
|
238
|
+
<View style={styles.backIconHead} />
|
|
239
|
+
</View>
|
|
240
|
+
)}
|
|
241
|
+
</TouchableOpacity>
|
|
242
|
+
|
|
243
|
+
<View style={styles.headerSpacer} />
|
|
244
|
+
|
|
245
|
+
{partnerLogo ? (
|
|
246
|
+
<Image
|
|
247
|
+
source={{ uri: partnerLogo }}
|
|
248
|
+
resizeMode="contain"
|
|
249
|
+
style={styles.logo}
|
|
250
|
+
/>
|
|
251
|
+
) : (
|
|
252
|
+
<View style={styles.logoPlaceholder} />
|
|
253
|
+
)}
|
|
254
|
+
</View>
|
|
255
|
+
<WebView
|
|
256
|
+
key={travelKey}
|
|
257
|
+
ref={travelRef}
|
|
258
|
+
source={travelSource}
|
|
259
|
+
originWhitelist={['*']}
|
|
260
|
+
applicationNameForUserAgent={WEBVIEW_UA_MARKER}
|
|
261
|
+
onMessage={onMessageTravel}
|
|
262
|
+
onLoadEnd={onLoadEndTravel}
|
|
263
|
+
style={styles.webview}
|
|
264
|
+
sharedCookiesEnabled
|
|
265
|
+
incognito={false}
|
|
266
|
+
webviewDebuggingEnabled={__DEV__}
|
|
267
|
+
/>
|
|
268
|
+
</View>
|
|
269
|
+
) : null}
|
|
270
|
+
</View>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const styles = StyleSheet.create({
|
|
275
|
+
root: { flex: 1 },
|
|
276
|
+
travelColumn: { flex: 1 },
|
|
277
|
+
webview: { flex: 1 },
|
|
278
|
+
header: {
|
|
279
|
+
flexDirection: 'row',
|
|
280
|
+
alignItems: 'center',
|
|
281
|
+
backgroundColor: '#fff',
|
|
282
|
+
paddingHorizontal: 16,
|
|
283
|
+
paddingTop: 12,
|
|
284
|
+
paddingBottom: 12,
|
|
285
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
286
|
+
borderBottomColor: '#E5E5E5',
|
|
287
|
+
},
|
|
288
|
+
backButton: {
|
|
289
|
+
width: 32,
|
|
290
|
+
height: 32,
|
|
291
|
+
justifyContent: 'center',
|
|
292
|
+
alignItems: 'center',
|
|
293
|
+
},
|
|
294
|
+
backIconWrapper: {
|
|
295
|
+
width: 20,
|
|
296
|
+
height: 16,
|
|
297
|
+
justifyContent: 'center',
|
|
298
|
+
position: 'relative',
|
|
299
|
+
},
|
|
300
|
+
backIconShaft: {
|
|
301
|
+
position: 'absolute',
|
|
302
|
+
left: 6,
|
|
303
|
+
right: 0,
|
|
304
|
+
height: 2,
|
|
305
|
+
backgroundColor: '#111827',
|
|
306
|
+
borderRadius: 1,
|
|
307
|
+
},
|
|
308
|
+
backIconHead: {
|
|
309
|
+
width: 10,
|
|
310
|
+
height: 10,
|
|
311
|
+
borderLeftWidth: 2,
|
|
312
|
+
borderBottomWidth: 2,
|
|
313
|
+
borderColor: '#111827',
|
|
314
|
+
transform: [{ rotate: '45deg' }],
|
|
315
|
+
marginLeft: 1,
|
|
316
|
+
},
|
|
317
|
+
headerSpacer: {
|
|
318
|
+
flex: 1,
|
|
319
|
+
},
|
|
320
|
+
logo: {
|
|
321
|
+
width: 120,
|
|
322
|
+
height: 32,
|
|
323
|
+
},
|
|
324
|
+
logoPlaceholder: {
|
|
325
|
+
width: 120,
|
|
326
|
+
height: 32,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
export default DualWebViewBridgeController;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages posted from either WebView via ReactNativeWebView.postMessage(string).
|
|
3
|
+
* Pages should JSON.stringify envelopes before posting.
|
|
4
|
+
*/
|
|
5
|
+
export type DualWebViewBridgeEnvelope = {
|
|
6
|
+
bridge: 'dual-webview';
|
|
7
|
+
/** Who sent this message (set by the page). */
|
|
8
|
+
from: 'savers' | 'travel';
|
|
9
|
+
/**
|
|
10
|
+
* relay — forward payload to the other WebView via injectJavaScript + CustomEvent.
|
|
11
|
+
* open_travel — show travel surface (reloads travel WebView).
|
|
12
|
+
* close_travel — same as native back (reloads Savers WebView).
|
|
13
|
+
*/
|
|
14
|
+
action: 'relay' | 'open_travel' | 'close_travel';
|
|
15
|
+
/** For relay: which surface should receive the event. */
|
|
16
|
+
to?: 'savers' | 'travel';
|
|
17
|
+
payload?: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function parseDualWebViewEnvelope(
|
|
21
|
+
raw: string
|
|
22
|
+
): DualWebViewBridgeEnvelope | null {
|
|
23
|
+
try {
|
|
24
|
+
const o = JSON.parse(raw) as DualWebViewBridgeEnvelope;
|
|
25
|
+
if (o?.bridge === 'dual-webview' && o?.from && o?.action) {
|
|
26
|
+
return o;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
/* not JSON */
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
package/src/utils/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ const injectedEnv = 'sandbox' as string;
|
|
|
7
7
|
const DEFAULT_ENVIRONMENT: Environment =
|
|
8
8
|
injectedEnv === 'production' ? 'production' : 'sandbox';
|
|
9
9
|
|
|
10
|
+
/** Hosted merchant web origins; aligned with `SaversSdkHostedEnvironment` via `setEnvironment` in SDK init. */
|
|
10
11
|
const ENV_URLS: Record<Environment, string> = {
|
|
11
12
|
sandbox: 'https://testm.saversapp.com/',
|
|
12
13
|
production: 'https://m.saversapp.com/',
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// Platform Utilities
|
|
4
|
-
import { isAndroid } from "../../utils/platformManager.js";
|
|
5
|
-
import { resolveNativeDependencies } from "../../utils/dependencyManager.js";
|
|
6
|
-
|
|
7
|
-
// Permissions
|
|
8
|
-
import { ensureLocationPermission } from "../permissions/permissionManager.js";
|
|
9
|
-
let locationProvider;
|
|
10
|
-
export function setLocationProvider(provider) {
|
|
11
|
-
locationProvider = provider;
|
|
12
|
-
}
|
|
13
|
-
export const fetchLocation = async () => {
|
|
14
|
-
const {
|
|
15
|
-
Geolocation
|
|
16
|
-
} = resolveNativeDependencies();
|
|
17
|
-
if (!Geolocation) {
|
|
18
|
-
throw new Error('GEOLOCATION_NOT_LINKED: install and link @react-native-community/geolocation');
|
|
19
|
-
}
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
Geolocation.getCurrentPosition(pos => resolve({
|
|
22
|
-
lat: pos.coords.latitude,
|
|
23
|
-
lng: pos.coords.longitude
|
|
24
|
-
}), err => reject(err), {
|
|
25
|
-
enableHighAccuracy: true,
|
|
26
|
-
timeout: 10000
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
export async function getDeviceLocation() {
|
|
31
|
-
if (locationProvider) {
|
|
32
|
-
return locationProvider();
|
|
33
|
-
}
|
|
34
|
-
if (isAndroid()) {
|
|
35
|
-
const granted = await ensureLocationPermission();
|
|
36
|
-
if (!granted) {
|
|
37
|
-
throw new Error('LOCATION_PERMISSION_DENIED');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const loc = await fetchLocation();
|
|
42
|
-
return loc;
|
|
43
|
-
} catch {
|
|
44
|
-
throw new Error('LOCATION_UNAVAILABLE');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=location.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["isAndroid","resolveNativeDependencies","ensureLocationPermission","locationProvider","setLocationProvider","provider","fetchLocation","Geolocation","Error","Promise","resolve","reject","getCurrentPosition","pos","lat","coords","latitude","lng","longitude","err","enableHighAccuracy","timeout","getDeviceLocation","granted","loc"],"sourceRoot":"../../../../src","sources":["services/device/location.ts"],"mappings":";;AAAA;AACA,SAASA,SAAS,QAAQ,gCAA6B;AACvD,SAASC,yBAAyB,QAAQ,kCAA+B;;AAEzE;AACA,SAASC,wBAAwB,QAAQ,qCAAkC;AAI3E,IAAIC,gBAAyE;AAE7E,OAAO,SAASC,mBAAmBA,CACjCC,QAAmD,EAC7C;EACNF,gBAAgB,GAAGE,QAAQ;AAC7B;AAEA,OAAO,MAAMC,aAAa,GAAG,MAAAA,CAAA,KAAqC;EAChE,MAAM;IAAEC;EAAY,CAAC,GAAGN,yBAAyB,CAAC,CAAC;EACnD,IAAI,CAACM,WAAW,EAAE;IAChB,MAAM,IAAIC,KAAK,CACb,8EACF,CAAC;EACH;EACA,OAAO,IAAIC,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;IACtCJ,WAAW,CAACK,kBAAkB,CAC3BC,GAAwD,IACvDH,OAAO,CAAC;MAAEI,GAAG,EAAED,GAAG,CAACE,MAAM,CAACC,QAAQ;MAAEC,GAAG,EAAEJ,GAAG,CAACE,MAAM,CAACG;IAAU,CAAC,CAAC,EACjEC,GAAY,IAAKR,MAAM,CAACQ,GAAG,CAAC,EAC7B;MAAEC,kBAAkB,EAAE,IAAI;MAAEC,OAAO,EAAE;IAAM,CAC7C,CAAC;EACH,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,eAAeC,iBAAiBA,CAAA,EAAwC;EAC7E,IAAInB,gBAAgB,EAAE;IACpB,OAAOA,gBAAgB,CAAC,CAAC;EAC3B;EACA,IAAIH,SAAS,CAAC,CAAC,EAAE;IACf,MAAMuB,OAAO,GAAG,MAAMrB,wBAAwB,CAAC,CAAC;IAChD,IAAI,CAACqB,OAAO,EAAE;MACZ,MAAM,IAAIf,KAAK,CAAC,4BAA4B,CAAC;IAC/C;EACF;EACA,IAAI;IACF,MAAMgB,GAAG,GAAG,MAAMlB,aAAa,CAAC,CAAC;IACjC,OAAOkB,GAAG;EACZ,CAAC,CAAC,MAAM;IACN,MAAM,IAAIhB,KAAK,CAAC,sBAAsB,CAAC;EACzC;AACF","ignoreList":[]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export type DeviceLocation = {
|
|
2
|
-
lat: number;
|
|
3
|
-
lng: number;
|
|
4
|
-
};
|
|
5
|
-
export declare function setLocationProvider(provider: () => Promise<DeviceLocation | undefined>): void;
|
|
6
|
-
export declare const fetchLocation: () => Promise<DeviceLocation>;
|
|
7
|
-
export declare function getDeviceLocation(): Promise<DeviceLocation | undefined>;
|
|
8
|
-
//# sourceMappingURL=location.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../../../../../src/services/device/location.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAI1D,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,GAClD,IAAI,CAEN;AAED,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,cAAc,CAe5D,CAAC;AAEF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAgB7E"}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// Platform Utilities
|
|
2
|
-
import { isAndroid } from '../../utils/platformManager';
|
|
3
|
-
import { resolveNativeDependencies } from '../../utils/dependencyManager';
|
|
4
|
-
|
|
5
|
-
// Permissions
|
|
6
|
-
import { ensureLocationPermission } from '../permissions/permissionManager';
|
|
7
|
-
|
|
8
|
-
export type DeviceLocation = { lat: number; lng: number };
|
|
9
|
-
|
|
10
|
-
let locationProvider: (() => Promise<DeviceLocation | undefined>) | undefined;
|
|
11
|
-
|
|
12
|
-
export function setLocationProvider(
|
|
13
|
-
provider: () => Promise<DeviceLocation | undefined>
|
|
14
|
-
): void {
|
|
15
|
-
locationProvider = provider;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const fetchLocation = async (): Promise<DeviceLocation> => {
|
|
19
|
-
const { Geolocation } = resolveNativeDependencies();
|
|
20
|
-
if (!Geolocation) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'GEOLOCATION_NOT_LINKED: install and link @react-native-community/geolocation'
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
Geolocation.getCurrentPosition(
|
|
27
|
-
(pos: { coords: { latitude: number; longitude: number } }) =>
|
|
28
|
-
resolve({ lat: pos.coords.latitude, lng: pos.coords.longitude }),
|
|
29
|
-
(err: unknown) => reject(err),
|
|
30
|
-
{ enableHighAccuracy: true, timeout: 10000 }
|
|
31
|
-
);
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export async function getDeviceLocation(): Promise<DeviceLocation | undefined> {
|
|
36
|
-
if (locationProvider) {
|
|
37
|
-
return locationProvider();
|
|
38
|
-
}
|
|
39
|
-
if (isAndroid()) {
|
|
40
|
-
const granted = await ensureLocationPermission();
|
|
41
|
-
if (!granted) {
|
|
42
|
-
throw new Error('LOCATION_PERMISSION_DENIED');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const loc = await fetchLocation();
|
|
47
|
-
return loc;
|
|
48
|
-
} catch {
|
|
49
|
-
throw new Error('LOCATION_UNAVAILABLE');
|
|
50
|
-
}
|
|
51
|
-
}
|