@sanvika/geolocation 0.6.2 → 0.6.4

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.2",
3
+ "version": "0.6.4",
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",
@@ -16,7 +16,7 @@ import { createLogger } from "@sanvika/logger";
16
16
  const logger = createLogger({ namespace: "SanvikaGeolocation.MapsLoader" });
17
17
 
18
18
  let inflight = null;
19
- let cachedConfig = null;
19
+ const cachedConfigByPlatform = new Map();
20
20
 
21
21
  function readEnv(key) {
22
22
  try {
@@ -26,17 +26,22 @@ function readEnv(key) {
26
26
  }
27
27
  }
28
28
 
29
- async function fetchMapsConfig({ apiBase, clientId, mapsConfigPath }) {
30
- if (cachedConfig) return cachedConfig;
29
+ async function fetchMapsConfig({ apiBase, clientId, mapsConfigPath, platform = "web" }) {
30
+ const cacheKey = `${clientId}:${platform}`;
31
+ if (cachedConfigByPlatform.has(cacheKey)) return cachedConfigByPlatform.get(cacheKey);
31
32
  const configPath = mapsConfigPath || "/api/v1/mapsjs/config";
32
- const url = `${apiBase}${configPath}?clientId=${encodeURIComponent(clientId)}`;
33
+ const params = new URLSearchParams({
34
+ clientId,
35
+ platform,
36
+ });
37
+ const url = `${apiBase}${configPath}?${params.toString()}`;
33
38
  const res = await fetch(url, { credentials: "omit" });
34
39
  const data = await res.json().catch(() => ({}));
35
40
  if (!res.ok || !data?.success) {
36
41
  throw new Error(`mapsjs/config failed: ${data?.error || `HTTP_${res.status}`}`);
37
42
  }
38
- cachedConfig = data.config;
39
- return cachedConfig;
43
+ cachedConfigByPlatform.set(cacheKey, data.config);
44
+ return data.config;
40
45
  }
41
46
 
42
47
  export async function loadGoogleMapsScript({
@@ -45,6 +50,7 @@ export async function loadGoogleMapsScript({
45
50
  apiBase: apiBaseInput,
46
51
  clientId: clientIdInput,
47
52
  mapsConfigPath,
53
+ platform = "web",
48
54
  } = {}) {
49
55
  if (typeof window === "undefined") {
50
56
  throw new Error("loadGoogleMapsScript must run in the browser");
@@ -66,7 +72,7 @@ export async function loadGoogleMapsScript({
66
72
  }
67
73
 
68
74
  inflight = (async () => {
69
- const config = await fetchMapsConfig({ apiBase, clientId, mapsConfigPath });
75
+ const config = await fetchMapsConfig({ apiBase, clientId, mapsConfigPath, platform });
70
76
 
71
77
  return new Promise((resolve, reject) => {
72
78
  const callbackName = `__sanvika_maps_cb_${Date.now()}`;
@@ -120,11 +126,15 @@ export async function loadGoogleMapsScript({
120
126
  }
121
127
  }
122
128
 
123
- export function getMapsConfig() {
124
- return cachedConfig;
129
+ export function getMapsConfig(platform = "web") {
130
+ const clientId =
131
+ readEnv("NEXT_PUBLIC_CLIENT_ID") ||
132
+ readEnv("NEXT_PUBLIC_GEOLOCATION_CLIENT_ID") ||
133
+ "default";
134
+ return cachedConfigByPlatform.get(`${clientId}:${platform}`) || null;
125
135
  }
126
136
 
127
137
  export function resetMapsLoader() {
128
- cachedConfig = null;
138
+ cachedConfigByPlatform.clear();
129
139
  inflight = null;
130
140
  }
@@ -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 fetchUserDatabaseLocation(uid, apiBaseUrl, log) {
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(`${url}?uid=${encodeURIComponent(uid)}`, {
27
- headers: { uid, Accept: "application/json" },
49
+ const res = await fetch(url, {
50
+ headers: {
51
+ Accept: "application/json",
52
+ Authorization: `Bearer ${token}`,
53
+ },
28
54
  });
29
- if (!res.ok) { return null; }
55
+ if (!res.ok) {
56
+ return null;
57
+ }
30
58
  const data = await res.json();
31
- if (!data?.success || !data?.data) { return null; }
59
+ if (!data?.success || !data?.data) {
60
+ return null;
61
+ }
32
62
  const coords = normalizeCoords(data.data.latitude, data.data.longitude);
33
- if (!coords) { return null; }
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) { return null; }
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) { return null; }
117
+ if (!res.ok) {
118
+ return null;
119
+ }
84
120
  const data = await res.json();
85
- if (!data?.success || !data?.location) { return null; }
121
+ if (!data?.success || !data?.location) {
122
+ return null;
123
+ }
86
124
  const coords = normalizeCoords(data.location.latitude, data.location.longitude);
87
- if (!coords) { return null; }
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 {{ userUid?: string, apiBaseUrl?: string, useDeviceLocation?: boolean, useIpFallback?: boolean, logger?: object }} opts
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(userUid, apiBaseUrl, log);
110
- if (dbLocation) { return dbLocation; }
111
- log.warn("User DB location unavailable", { uid: userUid });
112
- return null;
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) { return 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) { return ipLocation; }
178
+ if (ipLocation) {
179
+ return ipLocation;
180
+ }
123
181
  }
124
182
 
125
183
  log.warn("All location sources exhausted");
@@ -96,6 +96,14 @@ export async function placesDetails(placeId, { language = "en", fields, sessionT
96
96
  });
97
97
  }
98
98
 
99
+ /** S2S — resolve platform-specific Google Map ID (web | android | ios | static). */
100
+ export async function getMapsPlatformConfig(platform = "web") {
101
+ return getServiceClient().request("/api/v1/maps/config", {
102
+ method: "GET",
103
+ query: { platform },
104
+ });
105
+ }
106
+
99
107
  /**
100
108
  * Detect Indian state + city from coordinates.
101
109
  * Now proxied through reverseGeocode (which calls geolocation service) — no Google key in consumer env.