@fleetbase/ember-core 0.3.11 → 0.3.12

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.
@@ -8,6 +8,7 @@ import { isBlank } from '@ember/utils';
8
8
  import { alias } from '@ember/object/computed';
9
9
  import { storageFor } from 'ember-local-storage';
10
10
  import { debug } from '@ember/debug';
11
+ import lookupUserIp from '../utils/lookup-user-ip';
11
12
 
12
13
  export default class CurrentUserService extends Service.extend(Evented) {
13
14
  @service session;
@@ -139,23 +140,34 @@ export default class CurrentUserService extends Service.extend(Evented) {
139
140
  }
140
141
 
141
142
  async loadWhois() {
142
- this.fetch.shouldResetCache();
143
-
144
143
  try {
145
- const whois = await this.fetch.cachedGet(
146
- 'lookup/whois',
147
- {},
148
- {
149
- expirationInterval: 60,
150
- expirationIntervalUnit: 'minutes',
151
- }
152
- );
144
+ // Use frontend IP lookup to get accurate user location
145
+ // This avoids the issue of server-side lookup returning server IP instead of user IP
146
+ const whois = await lookupUserIp({
147
+ timeout: 5000,
148
+ cache: true,
149
+ });
150
+
153
151
  this.setOption('whois', whois);
154
152
  this.whoisData = whois;
155
153
 
156
154
  return whois;
157
155
  } catch (error) {
158
- this.notifications.serverError(error);
156
+ console.error('[currentUser] Failed to load whois:', error);
157
+ this.notifications.warning('Unable to detect your location. Some features may use default settings.');
158
+
159
+ // Return fallback data with browser timezone
160
+ const fallback = {
161
+ city: null,
162
+ country_code: null,
163
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
164
+ _source: 'fallback',
165
+ };
166
+
167
+ this.setOption('whois', fallback);
168
+ this.whoisData = fallback;
169
+
170
+ return fallback;
159
171
  }
160
172
  }
161
173
 
@@ -33,7 +33,6 @@ export default class UniverseService extends Service.extend(Evented) {
33
33
  @service router;
34
34
  @service intl;
35
35
  @service urlSearchParams;
36
-
37
36
  @tracked applicationInstance;
38
37
  @tracked initialLocation = { ...window.location };
39
38
  @tracked bootCallbacks = A([]);
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Lookup user's IP address and geolocation information using geoiplookup.io
3
+ *
4
+ * This method calls the geoiplookup.io API directly from the browser to get
5
+ * accurate user location data, avoiding the issue of server-side IP lookup
6
+ * returning the server's location instead of the user's location.
7
+ *
8
+ * @param {Object} options - Configuration options
9
+ * @param {number} options.timeout - Request timeout in milliseconds (default: 5000)
10
+ * @param {boolean} options.cache - Whether to cache the result (default: true)
11
+ * @returns {Promise<Object>} User's IP and geolocation data
12
+ *
13
+ * @example
14
+ * const whois = await lookupUserIp();
15
+ * console.log(whois.city); // "New York"
16
+ * console.log(whois.country_code); // "US"
17
+ */
18
+ export default async function lookupUserIp(options = {}) {
19
+ const { timeout = 5000, cache = true } = options;
20
+
21
+ // Check cache first if enabled
22
+ if (cache) {
23
+ const cached = getCachedWhois();
24
+ if (cached) {
25
+ return cached;
26
+ }
27
+ }
28
+
29
+ // Try multiple APIs with fallback
30
+ const apis = [
31
+ {
32
+ url: 'https://json.geoiplookup.io/',
33
+ normalize: normalizeGeoIPLookup,
34
+ },
35
+ {
36
+ url: 'https://ipapi.co/json/',
37
+ normalize: normalizeIPApi,
38
+ },
39
+ ];
40
+
41
+ for (const api of apis) {
42
+ try {
43
+ const controller = new AbortController();
44
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
45
+
46
+ const response = await fetch(api.url, {
47
+ signal: controller.signal,
48
+ headers: {
49
+ Accept: 'application/json',
50
+ },
51
+ });
52
+
53
+ clearTimeout(timeoutId);
54
+
55
+ if (!response.ok) {
56
+ console.warn(`[lookupUserIp] ${api.url} returned ${response.status}`);
57
+ continue;
58
+ }
59
+
60
+ const data = await response.json();
61
+ const normalized = api.normalize(data);
62
+
63
+ // Cache the result if enabled
64
+ if (cache) {
65
+ cacheWhois(normalized);
66
+ }
67
+
68
+ return normalized;
69
+ } catch (error) {
70
+ console.warn(`[lookupUserIp] ${api.url} failed:`, error.message);
71
+ // Continue to next API
72
+ }
73
+ }
74
+
75
+ // All APIs failed, return fallback
76
+ console.error('[lookupUserIp] All IP lookup APIs failed, using fallback data');
77
+ return getFallbackWhois();
78
+ }
79
+
80
+ /**
81
+ * Normalize geoiplookup.io response to match Fleetbase whois format
82
+ */
83
+ function normalizeGeoIPLookup(data) {
84
+ return {
85
+ ip: data.ip,
86
+ city: data.city,
87
+ region: data.region,
88
+ country_code: data.country_code,
89
+ country_name: data.country_name,
90
+ continent_code: data.continent_code,
91
+ continent_name: data.continent_name,
92
+ latitude: data.latitude,
93
+ longitude: data.longitude,
94
+ postal_code: data.postal_code,
95
+ timezone: data.timezone_name,
96
+ currency: {
97
+ code: data.currency_code,
98
+ name: data.currency_name,
99
+ },
100
+ languages: [
101
+ {
102
+ code: data.language_code,
103
+ name: data.language_name,
104
+ },
105
+ ],
106
+ isp: data.isp,
107
+ org: data.org,
108
+ asn: data.asn,
109
+ connection_type: data.connection_type,
110
+ // Metadata
111
+ _source: 'geoiplookup.io',
112
+ _timestamp: Date.now(),
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Normalize ipapi.co response to match Fleetbase whois format
118
+ */
119
+ function normalizeIPApi(data) {
120
+ return {
121
+ ip: data.ip,
122
+ city: data.city,
123
+ region: data.region,
124
+ country_code: data.country_code,
125
+ country_name: data.country_name,
126
+ continent_code: data.continent_code,
127
+ continent_name: null,
128
+ latitude: data.latitude,
129
+ longitude: data.longitude,
130
+ postal_code: data.postal,
131
+ timezone: data.timezone,
132
+ currency: {
133
+ code: data.currency,
134
+ name: data.currency_name,
135
+ },
136
+ languages: data.languages ? data.languages.split(',').map((lang) => ({ code: lang.trim(), name: lang.trim() })) : [],
137
+ isp: data.org,
138
+ org: data.org,
139
+ asn: data.asn,
140
+ connection_type: null,
141
+ // Metadata
142
+ _source: 'ipapi.co',
143
+ _timestamp: Date.now(),
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Get cached whois data from localStorage
149
+ */
150
+ function getCachedWhois() {
151
+ try {
152
+ const cached = localStorage.getItem('fleetbase:whois');
153
+ if (!cached) {
154
+ return null;
155
+ }
156
+
157
+ const data = JSON.parse(cached);
158
+ const age = Date.now() - data._timestamp;
159
+ const maxAge = 60 * 60 * 1000; // 1 hour
160
+
161
+ if (age > maxAge) {
162
+ localStorage.removeItem('fleetbase:whois');
163
+ return null;
164
+ }
165
+
166
+ return data;
167
+ } catch (error) {
168
+ console.error('[getCachedWhois] Error reading cache:', error);
169
+ return null;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Cache whois data to localStorage
175
+ */
176
+ function cacheWhois(data) {
177
+ try {
178
+ localStorage.setItem('fleetbase:whois', JSON.stringify(data));
179
+ } catch (error) {
180
+ console.error('[cacheWhois] Error writing cache:', error);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Get fallback whois data when all APIs fail
186
+ */
187
+ function getFallbackWhois() {
188
+ // Try to get browser language and timezone as fallback
189
+ const browserLang = navigator.language || 'en-US';
190
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
191
+
192
+ return {
193
+ ip: null,
194
+ city: null,
195
+ region: null,
196
+ country_code: null,
197
+ country_name: null,
198
+ continent_code: null,
199
+ continent_name: null,
200
+ latitude: null,
201
+ longitude: null,
202
+ postal_code: null,
203
+ timezone: timezone,
204
+ currency: {
205
+ code: null,
206
+ name: null,
207
+ },
208
+ languages: [
209
+ {
210
+ code: browserLang.split('-')[0],
211
+ name: browserLang,
212
+ },
213
+ ],
214
+ isp: null,
215
+ org: null,
216
+ asn: null,
217
+ connection_type: null,
218
+ _source: 'fallback',
219
+ _timestamp: Date.now(),
220
+ };
221
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-core",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
5
  "keywords": [
6
6
  "fleetbase-core",