@sanvika/geolocation 0.6.1 → 0.6.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanvika/geolocation",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Sanvika ecosystem geolocation SDK — fully centralized via geolocation.sanvikaproduction.com. IP lookup, reverse/forward geocoding, Places autocomplete + details, Maps JS loader. Zero Google API keys in consumer projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -26,9 +26,10 @@ function readEnv(key) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async function fetchMapsConfig({ apiBase, clientId }) {
|
|
29
|
+
async function fetchMapsConfig({ apiBase, clientId, mapsConfigPath }) {
|
|
30
30
|
if (cachedConfig) return cachedConfig;
|
|
31
|
-
const
|
|
31
|
+
const configPath = mapsConfigPath || "/api/v1/mapsjs/config";
|
|
32
|
+
const url = `${apiBase}${configPath}?clientId=${encodeURIComponent(clientId)}`;
|
|
32
33
|
const res = await fetch(url, { credentials: "omit" });
|
|
33
34
|
const data = await res.json().catch(() => ({}));
|
|
34
35
|
if (!res.ok || !data?.success) {
|
|
@@ -43,6 +44,7 @@ export async function loadGoogleMapsScript({
|
|
|
43
44
|
version = "weekly",
|
|
44
45
|
apiBase: apiBaseInput,
|
|
45
46
|
clientId: clientIdInput,
|
|
47
|
+
mapsConfigPath,
|
|
46
48
|
} = {}) {
|
|
47
49
|
if (typeof window === "undefined") {
|
|
48
50
|
throw new Error("loadGoogleMapsScript must run in the browser");
|
|
@@ -64,7 +66,7 @@ export async function loadGoogleMapsScript({
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
inflight = (async () => {
|
|
67
|
-
const config = await fetchMapsConfig({ apiBase, clientId });
|
|
69
|
+
const config = await fetchMapsConfig({ apiBase, clientId, mapsConfigPath });
|
|
68
70
|
|
|
69
71
|
return new Promise((resolve, reject) => {
|
|
70
72
|
const callbackName = `__sanvika_maps_cb_${Date.now()}`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Module-level RN runtime — same pattern as @sanvika/realtime/chat configureChatClient.
|
|
2
|
+
|
|
3
|
+
/** @type {{ getAccessToken?: () => (string | null | Promise<string | null>) }} */
|
|
4
|
+
let geolocationRuntimeConfig = {
|
|
5
|
+
getAccessToken: null,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configure React Native geolocation client (call once at app start, e.g. App.js).
|
|
10
|
+
* Required when resolveLocation({ userUid }) calls authenticated FAPK routes.
|
|
11
|
+
*
|
|
12
|
+
* @param {{ getAccessToken?: () => (string | null | Promise<string | null>) }} opts
|
|
13
|
+
*/
|
|
14
|
+
export function configureGeolocationClient(opts = {}) {
|
|
15
|
+
if (typeof opts.getAccessToken === "function") {
|
|
16
|
+
geolocationRuntimeConfig.getAccessToken = opts.getAccessToken;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getGeolocationRuntimeConfig() {
|
|
21
|
+
return geolocationRuntimeConfig;
|
|
22
|
+
}
|
|
@@ -17,5 +17,6 @@ export {
|
|
|
17
17
|
export { setWithTTL, getWithTTL, TTL } from "./asyncStorageTTL.js";
|
|
18
18
|
|
|
19
19
|
// RN-native resolveLocation (replaces local mobile utils/location/resolveLocation.js)
|
|
20
|
+
export { configureGeolocationClient } from "./configureGeolocationClient.js";
|
|
20
21
|
export { resolveLocation } from "./resolveLocation.js";
|
|
21
22
|
export { resolveLocation as default } from "./resolveLocation.js";
|
|
@@ -5,6 +5,7 @@ import Geolocation from "@react-native-community/geolocation";
|
|
|
5
5
|
import { Platform } from "react-native";
|
|
6
6
|
import { request, PERMISSIONS, RESULTS } from "react-native-permissions";
|
|
7
7
|
import { createLogger } from "@sanvika/logger";
|
|
8
|
+
import { getGeolocationRuntimeConfig } from "./configureGeolocationClient.js";
|
|
8
9
|
|
|
9
10
|
const FAPK_API = "https://freeadpostkaro.com/api";
|
|
10
11
|
|
|
@@ -20,17 +21,48 @@ function normalizeCoords(lat, lng) {
|
|
|
20
21
|
return isValidCoord(latitude) && isValidCoord(longitude) ? { latitude, longitude } : null;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
async function
|
|
24
|
+
async function resolveAccessToken(getAccessTokenOverride) {
|
|
25
|
+
const getter =
|
|
26
|
+
getAccessTokenOverride || getGeolocationRuntimeConfig().getAccessToken;
|
|
27
|
+
if (typeof getter !== "function") {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const token = await getter();
|
|
32
|
+
return token ? String(token).trim() : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function fetchUserDatabaseLocation(uid, apiBaseUrl, log, getAccessTokenOverride) {
|
|
24
39
|
try {
|
|
40
|
+
const token = await resolveAccessToken(getAccessTokenOverride);
|
|
41
|
+
if (!token) {
|
|
42
|
+
log.warn("User DB location requires Bearer token — configureGeolocationClient({ getAccessToken })", {
|
|
43
|
+
uid,
|
|
44
|
+
});
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
25
48
|
const url = `${apiBaseUrl}/location/getUserLocation`;
|
|
26
|
-
const res = await fetch(
|
|
27
|
-
headers: {
|
|
49
|
+
const res = await fetch(url, {
|
|
50
|
+
headers: {
|
|
51
|
+
Accept: "application/json",
|
|
52
|
+
Authorization: `Bearer ${token}`,
|
|
53
|
+
},
|
|
28
54
|
});
|
|
29
|
-
if (!res.ok) {
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
30
58
|
const data = await res.json();
|
|
31
|
-
if (!data?.success || !data?.data) {
|
|
59
|
+
if (!data?.success || !data?.data) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
32
62
|
const coords = normalizeCoords(data.data.latitude, data.data.longitude);
|
|
33
|
-
if (!coords) {
|
|
63
|
+
if (!coords) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
34
66
|
return { ...data.data, ...coords, source: "user_database", priority: PRIORITY.USER_DATABASE };
|
|
35
67
|
} catch (err) {
|
|
36
68
|
log.error("User DB location fetch failed", { uid, message: err?.message });
|
|
@@ -60,7 +92,9 @@ async function fetchDeviceLocation(log) {
|
|
|
60
92
|
});
|
|
61
93
|
|
|
62
94
|
const coords = normalizeCoords(position?.coords?.latitude, position?.coords?.longitude);
|
|
63
|
-
if (!coords) {
|
|
95
|
+
if (!coords) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
64
98
|
|
|
65
99
|
return {
|
|
66
100
|
...coords,
|
|
@@ -80,11 +114,17 @@ async function fetchIpLocation(apiBaseUrl, log) {
|
|
|
80
114
|
const res = await fetch(`${apiBaseUrl}/location/getIpLocation`, {
|
|
81
115
|
headers: { Accept: "application/json" },
|
|
82
116
|
});
|
|
83
|
-
if (!res.ok) {
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
84
120
|
const data = await res.json();
|
|
85
|
-
if (!data?.success || !data?.location) {
|
|
121
|
+
if (!data?.success || !data?.location) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
86
124
|
const coords = normalizeCoords(data.location.latitude, data.location.longitude);
|
|
87
|
-
if (!coords) {
|
|
125
|
+
if (!coords) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
88
128
|
return { ...data.location, ...coords, source: "ip_location", priority: PRIORITY.IP };
|
|
89
129
|
} catch (err) {
|
|
90
130
|
log.warn("IP location fetch failed", { message: err?.message });
|
|
@@ -94,32 +134,50 @@ async function fetchIpLocation(apiBaseUrl, log) {
|
|
|
94
134
|
|
|
95
135
|
/**
|
|
96
136
|
* Resolve the best available location.
|
|
97
|
-
* @param {{
|
|
137
|
+
* @param {{
|
|
138
|
+
* userUid?: string,
|
|
139
|
+
* apiBaseUrl?: string,
|
|
140
|
+
* useDeviceLocation?: boolean,
|
|
141
|
+
* useIpFallback?: boolean,
|
|
142
|
+
* getAccessToken?: () => (string | null | Promise<string | null>),
|
|
143
|
+
* logger?: object,
|
|
144
|
+
* }} opts
|
|
98
145
|
*/
|
|
99
146
|
export async function resolveLocation({
|
|
100
147
|
userUid,
|
|
101
148
|
apiBaseUrl = FAPK_API,
|
|
102
149
|
useDeviceLocation = false,
|
|
103
150
|
useIpFallback = false,
|
|
151
|
+
getAccessToken,
|
|
104
152
|
logger,
|
|
105
153
|
} = {}) {
|
|
106
154
|
const log = ensureLog(logger);
|
|
107
155
|
|
|
108
156
|
if (userUid) {
|
|
109
|
-
const dbLocation = await fetchUserDatabaseLocation(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
const dbLocation = await fetchUserDatabaseLocation(
|
|
158
|
+
userUid,
|
|
159
|
+
apiBaseUrl,
|
|
160
|
+
log,
|
|
161
|
+
getAccessToken,
|
|
162
|
+
);
|
|
163
|
+
if (dbLocation) {
|
|
164
|
+
return dbLocation;
|
|
165
|
+
}
|
|
166
|
+
log.warn("User DB location unavailable — falling back to device/IP", { uid: userUid });
|
|
113
167
|
}
|
|
114
168
|
|
|
115
169
|
if (useDeviceLocation) {
|
|
116
170
|
const deviceLocation = await fetchDeviceLocation(log);
|
|
117
|
-
if (deviceLocation) {
|
|
171
|
+
if (deviceLocation) {
|
|
172
|
+
return deviceLocation;
|
|
173
|
+
}
|
|
118
174
|
}
|
|
119
175
|
|
|
120
176
|
if (useIpFallback) {
|
|
121
177
|
const ipLocation = await fetchIpLocation(apiBaseUrl, log);
|
|
122
|
-
if (ipLocation) {
|
|
178
|
+
if (ipLocation) {
|
|
179
|
+
return ipLocation;
|
|
180
|
+
}
|
|
123
181
|
}
|
|
124
182
|
|
|
125
183
|
log.warn("All location sources exhausted");
|