@eshal-bot/chat-widget 0.1.46 → 0.1.48

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.
@@ -8454,9 +8454,9 @@
8454
8454
  }
8455
8455
  };
8456
8456
 
8457
- /**
8458
- * Initializes the PCM audio player worklet that streams inline PCM chunks
8459
- * coming from the ADK websocket events.
8457
+ /**
8458
+ * Initializes the PCM audio player worklet that streams inline PCM chunks
8459
+ * coming from the ADK websocket events.
8460
8460
  */
8461
8461
  async function startAudioPlayerWorklet() {
8462
8462
  if (typeof window === 'undefined') {
@@ -8480,9 +8480,9 @@
8480
8480
  return [audioPlayerNode, audioContext];
8481
8481
  }
8482
8482
 
8483
- /**
8484
- * Bootstraps the PCM recorder audio worklet, captures mic audio at 16kHz
8485
- * mono, and forwards buffers through the provided handler.
8483
+ /**
8484
+ * Bootstraps the PCM recorder audio worklet, captures mic audio at 16kHz
8485
+ * mono, and forwards buffers through the provided handler.
8486
8486
  */
8487
8487
  async function startAudioRecorderWorklet(onPcmChunk) {
8488
8488
  if (typeof window === 'undefined') {
@@ -8519,8 +8519,8 @@
8519
8519
  return [audioRecorderNode, audioRecorderContext, micStream];
8520
8520
  }
8521
8521
 
8522
- /**
8523
- * Stops all microphone tracks.
8522
+ /**
8523
+ * Stops all microphone tracks.
8524
8524
  */
8525
8525
  function stopMicrophone(stream) {
8526
8526
  if (!stream) {
@@ -8537,24 +8537,24 @@
8537
8537
  return pcm16Array.buffer;
8538
8538
  }
8539
8539
 
8540
- /**
8541
- * Client-side Voice-Activity gate for the widget's voice (Bidi) session.
8542
- *
8543
- * Ported from packages/agent-sdk/src/voice/audio/vad.ts (`createSpeechGate`),
8544
- * which was itself originally derived from this widget. The gate sits between
8545
- * the mic worklet and the WebSocket: it forwards PCM ONLY inside a confirmed
8546
- * speech turn and fires `onSpeechEnd` when the turn closes, so the caller can
8547
- * tell the backend the utterance is done (an `audio_end` signal).
8548
- *
8549
- * Why the widget needs this: knowledge-api's non-streaming voice path only
8550
- * transcribes a turn when it receives `audio_end` (or its flaky energy-VAD
8551
- * backstop trips). Without a client gate the widget streamed mic audio forever
8552
- * and the backend just buffered it ("Collecting Audio…") without ever
8553
- * transcribing — so voice connected but never replied. The agent-sdk already
8554
- * gates this way; this brings the widget to parity.
8555
- *
8556
- * Framework-free and side-effect-free (only calls the two callbacks) so it can
8557
- * be reasoned about / tested in isolation.
8540
+ /**
8541
+ * Client-side Voice-Activity gate for the widget's voice (Bidi) session.
8542
+ *
8543
+ * Ported from packages/agent-sdk/src/voice/audio/vad.ts (`createSpeechGate`),
8544
+ * which was itself originally derived from this widget. The gate sits between
8545
+ * the mic worklet and the WebSocket: it forwards PCM ONLY inside a confirmed
8546
+ * speech turn and fires `onSpeechEnd` when the turn closes, so the caller can
8547
+ * tell the backend the utterance is done (an `audio_end` signal).
8548
+ *
8549
+ * Why the widget needs this: knowledge-api's non-streaming voice path only
8550
+ * transcribes a turn when it receives `audio_end` (or its flaky energy-VAD
8551
+ * backstop trips). Without a client gate the widget streamed mic audio forever
8552
+ * and the backend just buffered it ("Collecting Audio…") without ever
8553
+ * transcribing — so voice connected but never replied. The agent-sdk already
8554
+ * gates this way; this brings the widget to parity.
8555
+ *
8556
+ * Framework-free and side-effect-free (only calls the two callbacks) so it can
8557
+ * be reasoned about / tested in isolation.
8558
8558
  */
8559
8559
 
8560
8560
  const DEFAULT_SPEECH_GATE_CONFIG = {
@@ -8583,16 +8583,16 @@
8583
8583
  return Math.sqrt(sumSquares / samples.length);
8584
8584
  }
8585
8585
 
8586
- /**
8587
- * Create a stateful speech gate. Returns `{ process, reset, isSpeaking }`.
8588
- * `config.sampleRate` is required; everything else falls back to
8589
- * DEFAULT_SPEECH_GATE_CONFIG.
8590
- *
8591
- * @param {{ sampleRate: number, energyThreshold?: number, speechStartMs?: number, hangoverMs?: number, preRollMs?: number }} config
8592
- * @param {{ onSpeechStart?: () => void, onVoiceFrame: (pcm: ArrayBuffer) => void, onSpeechEnd: () => void }} callbacks
8593
- * onSpeechStart fires the instant a turn opens — use it for barge-in
8594
- * (stop any assistant TTS still playing). onVoiceFrame emits each voiced
8595
- * PCM frame; onSpeechEnd fires when the turn closes after the hangover.
8586
+ /**
8587
+ * Create a stateful speech gate. Returns `{ process, reset, isSpeaking }`.
8588
+ * `config.sampleRate` is required; everything else falls back to
8589
+ * DEFAULT_SPEECH_GATE_CONFIG.
8590
+ *
8591
+ * @param {{ sampleRate: number, energyThreshold?: number, speechStartMs?: number, hangoverMs?: number, preRollMs?: number }} config
8592
+ * @param {{ onSpeechStart?: () => void, onVoiceFrame: (pcm: ArrayBuffer) => void, onSpeechEnd: () => void }} callbacks
8593
+ * onSpeechStart fires the instant a turn opens — use it for barge-in
8594
+ * (stop any assistant TTS still playing). onVoiceFrame emits each voiced
8595
+ * PCM frame; onSpeechEnd fires when the turn closes after the hangover.
8596
8596
  */
8597
8597
  function createSpeechGate(config, callbacks) {
8598
8598
  var _config$energyThresho, _config$speechStartMs, _config$hangoverMs, _config$preRollMs;
@@ -9288,6 +9288,14 @@
9288
9288
  } = await Promise.resolve().then(function () { return index; });
9289
9289
  const tracer = new UserTrace({
9290
9290
  cache: true,
9291
+ // Capture the visitor's precise location via the browser Geolocation
9292
+ // API (behind the native permission prompt) and reverse-geocode it
9293
+ // into profile.geo, so "Traffic by location" reflects the real city
9294
+ // instead of the coarse/wrong IP-gateway city. Granted → accurate
9295
+ // GPS; denied/dismissed/plain-HTTP → graceful IP fallback. Set
9296
+ // explicitly (not relying on the SDK default) so the behavior is
9297
+ // durable even if @eshal-tracker/sdk changes its default later.
9298
+ geoFromBrowser: true,
9291
9299
  onError: () => {/* swallow — fingerprint is best-effort */}
9292
9300
  });
9293
9301
  const profile = await tracer.init();
@@ -59479,8 +59487,8 @@
59479
59487
  });
59480
59488
  };
59481
59489
 
59482
- /**
59483
- * Auto-scroll hook that scrolls to bottom when dependencies change
59490
+ /**
59491
+ * Auto-scroll hook that scrolls to bottom when dependencies change
59484
59492
  */
59485
59493
  function useAutoScroll(containerRef) {
59486
59494
  let dependencies = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
@@ -60081,6 +60089,21 @@
60081
60089
  // Handle X button: first click shows form, second click closes
60082
60090
  const handleCloseClick = reactExports.useCallback(() => {
60083
60091
  if (!isCsatEnabled) {
60092
+ // CSAT disabled: closing the chat should also clear out the
60093
+ // conversation, mirroring the post-CSAT clear in
60094
+ // handleResolveSubmission. Without this the X button only hides
60095
+ // the window while the old transcript persists in the session and
60096
+ // re-appears on reopen / reload. Route through onRefreshChat (same
60097
+ // entry point the kebab "Refresh Chat" uses) so any change to the
60098
+ // refresh path applies here too, and clear widgetFilter explicitly.
60099
+ if (onWidgetFilterChange) {
60100
+ onWidgetFilterChange(null);
60101
+ }
60102
+ if (onRefreshChat) {
60103
+ onRefreshChat();
60104
+ } else if (onResetConversation) {
60105
+ onResetConversation();
60106
+ }
60084
60107
  onClose === null || onClose === void 0 || onClose();
60085
60108
  return;
60086
60109
  }
@@ -60092,7 +60115,7 @@
60092
60115
  } else {
60093
60116
  onClose === null || onClose === void 0 || onClose();
60094
60117
  }
60095
- }, [isCsatEnabled, showResolveForm, isConversationResolved, onClose]);
60118
+ }, [isCsatEnabled, showResolveForm, isConversationResolved, onClose, onWidgetFilterChange, onRefreshChat, onResetConversation]);
60096
60119
  reactExports.useEffect(() => {
60097
60120
  if (isCsatEnabled) return;
60098
60121
  if (showResolveForm) setShowResolveForm(false);
@@ -61924,6 +61947,16 @@
61924
61947
  })
61925
61948
  }];
61926
61949
 
61950
+ // Reorder the keyless cascade: freeipapi.com is currently the only one
61951
+ // reliably reachable from a browser — ip-api.com's free tier is HTTP-only
61952
+ // (→ HTTP 403 over HTTPS), ipwho.is → 403, ipapi.co → 429. Trying freeipapi
61953
+ // first avoids three doomed round-trips on every lookup. The rest stay as
61954
+ // fallback in case they recover or behave differently from another network.
61955
+ // NOTE: all IP providers locate the ISP gateway, not the device — for an
61956
+ // accurate end-user position use getBrowserLocation() (GPS + reverseGeocode).
61957
+ const FREE_ORDER = ["freeipapi.com", "ipwho.is", "ipapi.co", "ip-api.com"];
61958
+ freeProviders.sort((a, b) => FREE_ORDER.indexOf(a.name) - FREE_ORDER.indexOf(b.name));
61959
+
61927
61960
  // Key-based providers go first (more reliable, higher limits)
61928
61961
  return [...keyedProviders, ...freeProviders];
61929
61962
  }
@@ -62021,22 +62054,130 @@
62021
62054
  }));
62022
62055
  }
62023
62056
 
62057
+ // Default options for navigator.geolocation.getCurrentPosition.
62058
+ // enableHighAccuracy → prefer real GPS over coarse network positioning;
62059
+ // timeout 8s → don't hang forever waiting for a fix;
62060
+ // maximumAge 5 min → accept a recently-cached fix so a silent refresh
62061
+ // (permission already granted) resolves instantly.
62062
+ const DEFAULT_GEO_OPTIONS = {
62063
+ enableHighAccuracy: true,
62064
+ timeout: 8000,
62065
+ maximumAge: 5 * 60 * 1000
62066
+ };
62067
+
62024
62068
  /**
62025
- * Get browser-native geolocation (GPS / network-based, requires user permission)
62069
+ * Get browser-native geolocation (GPS / network-based).
62070
+ * Triggers the native permission prompt on first use for an origin (unless the
62071
+ * user already granted/denied). Resolves null — never rejects — on error,
62072
+ * denial, unsupported, or insecure (plain-HTTP) context, so callers can simply
62073
+ * fall back to IP geo.
62074
+ *
62075
+ * @param {PositionOptions} [options] - merged over DEFAULT_GEO_OPTIONS
62076
+ * @returns {Promise<{latitude:number, longitude:number, accuracy:number}|null>}
62026
62077
  */
62027
- function getBrowserGeolocation$1(options = {
62028
- timeout: 8000
62029
- }) {
62078
+ function getBrowserGeolocation$1(options = {}) {
62030
62079
  return new Promise(resolve => {
62031
- if (!navigator?.geolocation) return resolve(null);
62080
+ if (typeof navigator === "undefined" || !navigator.geolocation) return resolve(null);
62081
+ // getCurrentPosition only resolves in a secure context (HTTPS or
62082
+ // localhost). Bail early on plain HTTP rather than attempt a doomed prompt.
62083
+ if (typeof window !== "undefined" && window.isSecureContext === false) return resolve(null);
62032
62084
  navigator.geolocation.getCurrentPosition(pos => resolve({
62033
62085
  latitude: pos.coords.latitude,
62034
62086
  longitude: pos.coords.longitude,
62035
62087
  accuracy: pos.coords.accuracy
62036
- }), () => resolve(null), options);
62088
+ }), () => resolve(null), {
62089
+ ...DEFAULT_GEO_OPTIONS,
62090
+ ...options
62091
+ });
62037
62092
  });
62038
62093
  }
62039
62094
 
62095
+ /**
62096
+ * Query the current geolocation permission WITHOUT triggering a prompt.
62097
+ * Lets the host decide whether to ask: "granted" → fetch silently, "denied" →
62098
+ * skip (IP fallback), "prompt" → show your own pre-ask before getCurrentPosition.
62099
+ * Returns "unsupported" where the Permissions API is unavailable (e.g. older
62100
+ * Safari) so callers can treat it as "not known to be granted".
62101
+ * @returns {Promise<"granted"|"denied"|"prompt"|"unsupported">}
62102
+ */
62103
+ async function getGeolocationPermission$1() {
62104
+ try {
62105
+ if (typeof navigator === "undefined" || !navigator.permissions || !navigator.permissions.query) {
62106
+ return "unsupported";
62107
+ }
62108
+ const status = await navigator.permissions.query({
62109
+ name: "geolocation"
62110
+ });
62111
+ return status.state; // "granted" | "denied" | "prompt"
62112
+ } catch {
62113
+ return "unsupported";
62114
+ }
62115
+ }
62116
+
62117
+ /**
62118
+ * Reverse-geocode latitude/longitude → city / region / country.
62119
+ * Uses BigDataCloud's keyless, CORS-enabled *client* endpoint (no API key,
62120
+ * accurate to the city/state level). Resolves null on network failure or an
62121
+ * unusable response so the caller can keep any prior (IP-based) values.
62122
+ *
62123
+ * @param {number} latitude
62124
+ * @param {number} longitude
62125
+ * @param {Object} [options]
62126
+ * @param {number} [options.timeoutMs]
62127
+ * @param {string} [options.localityLanguage="en"]
62128
+ * @returns {Promise<{city,region,regionCode,country,countryCode,latitude,longitude}|null>}
62129
+ */
62130
+ async function reverseGeocode$1(latitude, longitude, options = {}) {
62131
+ if (typeof latitude !== "number" || typeof longitude !== "number") return null;
62132
+ const timeoutMs = options.timeoutMs || _globalConfig.timeoutMs || 5000;
62133
+ const lang = options.localityLanguage || "en";
62134
+ const url = "https://api.bigdatacloud.net/data/reverse-geocode-client" + `?latitude=${latitude}&longitude=${longitude}&localityLanguage=${lang}`;
62135
+ try {
62136
+ const d = await fetchWithTimeout(url, timeoutMs);
62137
+ const city = d.city || d.locality || "";
62138
+ // Treat an empty body (e.g. mid-ocean coords) as a miss, not a result.
62139
+ if (!city && !d.countryName) return null;
62140
+ return {
62141
+ city,
62142
+ region: d.principalSubdivision || "",
62143
+ regionCode: (d.principalSubdivisionCode || "").split("-").pop() || "",
62144
+ country: d.countryName || "",
62145
+ countryCode: d.countryCode || "",
62146
+ latitude,
62147
+ longitude
62148
+ };
62149
+ } catch {
62150
+ return null;
62151
+ }
62152
+ }
62153
+
62154
+ /**
62155
+ * High-level "exact location" helper. Gets the device GPS fix (prompting if
62156
+ * needed) and reverse-geocodes it. Returns a geo-shaped object tagged
62157
+ * source:"gps" when the place resolved; the bare coords (no source tag) when
62158
+ * only the fix succeeded but reverse-geocoding failed; or null when there is no
62159
+ * fix at all (denied / unsupported / insecure). This is the accurate-location
62160
+ * primitive — prefer it over IP geo for the real end-user position.
62161
+ *
62162
+ * @param {PositionOptions} [options]
62163
+ * @returns {Promise<Object|null>}
62164
+ */
62165
+ async function getBrowserLocation$1(options = {}) {
62166
+ const coords = await getBrowserGeolocation$1(options);
62167
+ if (!coords) return null;
62168
+ const place = await reverseGeocode$1(coords.latitude, coords.longitude, options);
62169
+ const hasPlace = !!(place && (place.city || place.country));
62170
+ return {
62171
+ ...(hasPlace ? place : {}),
62172
+ latitude: coords.latitude,
62173
+ longitude: coords.longitude,
62174
+ accuracy: coords.accuracy,
62175
+ ...(hasPlace ? {
62176
+ source: "gps"
62177
+ } : {})
62178
+ };
62179
+ }
62180
+
62040
62181
  /** Build a Google Maps URL from coordinates */
62041
62182
  function toMapsUrl$1(lat, lon) {
62042
62183
  return `https://www.google.com/maps?q=${lat},${lon}`;
@@ -62046,6 +62187,9 @@
62046
62187
  resolveLocation: resolveLocation$1,
62047
62188
  listProviders: listProviders$1,
62048
62189
  getBrowserGeolocation: getBrowserGeolocation$1,
62190
+ getGeolocationPermission: getGeolocationPermission$1,
62191
+ reverseGeocode: reverseGeocode$1,
62192
+ getBrowserLocation: getBrowserLocation$1,
62049
62193
  toMapsUrl: toMapsUrl$1
62050
62194
  };
62051
62195
 
@@ -62229,6 +62373,9 @@
62229
62373
  resolveLocation,
62230
62374
  listProviders,
62231
62375
  getBrowserGeolocation,
62376
+ getGeolocationPermission,
62377
+ reverseGeocode,
62378
+ getBrowserLocation,
62232
62379
  toMapsUrl
62233
62380
  } = location;
62234
62381
  const {
@@ -62261,7 +62408,13 @@
62261
62408
  * @param {Object} [options]
62262
62409
  * @param {Function} [options.onReady] - Callback when profile is ready
62263
62410
  * @param {Function} [options.onError] - Callback on error
62264
- * @param {boolean} [options.geoFromBrowser=false] - Also request GPS (asks permission)
62411
+ * @param {boolean|"auto"} [options.geoFromBrowser=true] - Capture precise GPS
62412
+ * and reverse-geocode it into `geo`. DEFAULT true = request on init()
62413
+ * (shows the native permission prompt unless already granted/denied);
62414
+ * false = never (IP only); "auto" = use GPS only when permission was
62415
+ * ALREADY granted (silent, never prompts).
62416
+ * @param {Object} [options.geoApiKeys] - API keys for keyed geo providers
62417
+ * @param {PositionOptions} [options.geoOptions] - Override getCurrentPosition opts
62265
62418
  * @param {boolean} [options.cache=true] - Use cached profile if < maxAgeMs old
62266
62419
  * @param {number} [options.maxAgeMs=300000] - Max cache age (default 5 min)
62267
62420
  * @param {boolean} [options.autoInit=false] - Auto-init on construction
@@ -62271,7 +62424,13 @@
62271
62424
  this._options = {
62272
62425
  onReady: options.onReady || null,
62273
62426
  onError: options.onError || null,
62274
- geoFromBrowser: options.geoFromBrowser || false,
62427
+ // Default ON: capture precise GPS on init() behind the browser's native
62428
+ // permission prompt. Use `=== undefined` (not `|| `) so an explicit
62429
+ // `false` opt-out is honored; pass "auto" to use an already-granted
62430
+ // permission silently without ever prompting.
62431
+ geoFromBrowser: options.geoFromBrowser === undefined ? true : options.geoFromBrowser,
62432
+ geoApiKeys: options.geoApiKeys || null,
62433
+ geoOptions: options.geoOptions || null,
62275
62434
  cache: options.cache !== false,
62276
62435
  maxAgeMs: options.maxAgeMs || 5 * 60 * 1000,
62277
62436
  autoInit: options.autoInit || false,
@@ -62321,15 +62480,46 @@
62321
62480
  confidence
62322
62481
  } = generateFingerprint();
62323
62482
 
62324
- // 3. IP + Geo — run in parallel
62325
- const [ipResult, browserGeo] = await Promise.all([detectIP(), this._options.geoFromBrowser ? getBrowserGeolocation() : Promise.resolve(null)]);
62326
- const ip = ipResult?.ip || "unknown";
62327
- const ipVersion = ipResult ? getIPVersion(ip) : "unknown";
62328
-
62329
- // 4. Resolve geo from IP
62330
- let geo = null;
62331
- if (ip !== "unknown") {
62332
- geo = await resolveLocation(ip);
62483
+ // 3. IP→geo chain and (optional) precise browser GPS run concurrently.
62484
+ // GPS is the slow path (permission prompt + satellite fix) so we never
62485
+ // block the IP lookup on it.
62486
+ const ipApiKeys = this._options.geoApiKeys ? {
62487
+ apiKeys: this._options.geoApiKeys
62488
+ } : {};
62489
+ const [ipBundle, gpsLoc] = await Promise.all([(async () => {
62490
+ const ipResult = await detectIP();
62491
+ const ipAddr = ipResult?.ip || "unknown";
62492
+ const ipGeo = ipAddr !== "unknown" ? await resolveLocation(ipAddr, ipApiKeys) : null;
62493
+ return {
62494
+ ip: ipAddr,
62495
+ ipVersion: ipResult ? getIPVersion(ipAddr) : "unknown",
62496
+ geo: ipGeo
62497
+ };
62498
+ })(), this._collectBrowserLocation()]);
62499
+ const {
62500
+ ip,
62501
+ ipVersion
62502
+ } = ipBundle;
62503
+
62504
+ // 4. Geo: start from the (coarse) IP location, then — when we have a
62505
+ // precise GPS fix — OVERRIDE city/region/country with the
62506
+ // reverse-geocoded values. IP geo only locates the ISP gateway, so a
62507
+ // real device fix always wins. Denied/unavailable → IP value stays.
62508
+ let geo = ipBundle.geo;
62509
+ let browserGeo = null;
62510
+ if (gpsLoc) {
62511
+ browserGeo = {
62512
+ latitude: gpsLoc.latitude,
62513
+ longitude: gpsLoc.longitude,
62514
+ accuracy: gpsLoc.accuracy
62515
+ };
62516
+ if (gpsLoc.source === "gps") {
62517
+ // gpsLoc carries city/region/regionCode/country/countryCode/lat/lng/source
62518
+ geo = {
62519
+ ...(geo || {}),
62520
+ ...gpsLoc
62521
+ };
62522
+ }
62333
62523
  }
62334
62524
 
62335
62525
  // 5. Browser context
@@ -62371,6 +62561,56 @@
62371
62561
  }
62372
62562
  }
62373
62563
 
62564
+ /**
62565
+ * Decide whether to capture browser GPS during init(), honoring geoFromBrowser:
62566
+ * false → null (skip)
62567
+ * "auto" → only when permission is ALREADY granted (silent, never prompts)
62568
+ * true → attempt (getCurrentPosition shows the prompt unless denied)
62569
+ * @private
62570
+ * @returns {Promise<Object|null>}
62571
+ */
62572
+ async _collectBrowserLocation() {
62573
+ const mode = this._options.geoFromBrowser;
62574
+ if (!mode) return null;
62575
+ if (mode === "auto") {
62576
+ const perm = await getGeolocationPermission();
62577
+ if (perm !== "granted") return null; // never prompt during a silent init
62578
+ }
62579
+ return getBrowserLocation(this._options.geoOptions || {});
62580
+ }
62581
+
62582
+ /**
62583
+ * Capture precise browser location AFTER your UI has obtained user consent
62584
+ * (e.g. an in-widget "Share your location?" pre-ask). Triggers the native
62585
+ * permission prompt, reverse-geocodes the fix, merges it into the current
62586
+ * profile's `geo` (source:"gps"), re-persists, and fires onReady. No-ops
62587
+ * gracefully when denied/unavailable (keeps the IP fallback) or before init().
62588
+ * @param {PositionOptions} [options]
62589
+ * @returns {Promise<UserProfile|null>}
62590
+ */
62591
+ async captureBrowserLocation(options = {}) {
62592
+ const loc = await getBrowserLocation({
62593
+ ...(this._options.geoOptions || {}),
62594
+ ...options
62595
+ });
62596
+ if (!loc || !this.profile) return this.profile; // denied/unavailable or not init'd yet
62597
+ this.profile.browserGeo = {
62598
+ latitude: loc.latitude,
62599
+ longitude: loc.longitude,
62600
+ accuracy: loc.accuracy
62601
+ };
62602
+ this.profile.mapsUrl = toMapsUrl(loc.latitude, loc.longitude);
62603
+ if (loc.source === "gps") {
62604
+ this.profile.geo = {
62605
+ ...(this.profile.geo || {}),
62606
+ ...loc
62607
+ };
62608
+ }
62609
+ saveSessionMeta(this.profile);
62610
+ if (this._options.onReady) this._options.onReady(this.profile);
62611
+ return this.profile;
62612
+ }
62613
+
62374
62614
  /**
62375
62615
  * Get the current profile (must call init() first)
62376
62616
  * @returns {UserProfile|null}
@@ -62465,6 +62705,9 @@
62465
62705
  src.exports.resolveLocation = resolveLocation;
62466
62706
  src.exports.listProviders = listProviders;
62467
62707
  src.exports.getBrowserGeolocation = getBrowserGeolocation;
62708
+ src.exports.getGeolocationPermission = getGeolocationPermission;
62709
+ src.exports.reverseGeocode = reverseGeocode;
62710
+ src.exports.getBrowserLocation = getBrowserLocation;
62468
62711
  src.exports.getOrCreateUserID = getOrCreateUserID;
62469
62712
  src.exports.getOrCreateSessionID = getOrCreateSessionID;
62470
62713
  src.exports.clearSession = clearSession;