paint-brush
Cách lấy vị trí của người dùng trong React.js: Hướng dẫn thực hànhby@codebucks
7,476
7,476

Cách lấy vị trí của người dùng trong React.js: Hướng dẫn thực hành

CodeBucks16m2023/04/25
Read on Terminal Reader

Các ứng dụng web ngày càng được cá nhân hóa. Có sự gia tăng của các ứng dụng web sử dụng các dịch vụ dựa trên vị trí vì nhiều lý do. Bằng cách biết vị trí của người dùng, bạn có thể dễ dàng cung cấp các tính năng khác nhau như nội dung được cá nhân hóa, bảo mật tốt, dự báo thời tiết, bản địa hóa nhiều hơn và trải nghiệm người dùng tốt hơn. Trong bài viết này, chúng tôi sẽ hướng dẫn bạn từng bước về cách bạn có thể lấy vị trí của người dùng bằng các công nghệ web hiện đại như React.
featured image - Cách lấy vị trí của người dùng trong React.js: Hướng dẫn thực hành
CodeBucks HackerNoon profile picture
0-item
1-item
2-item

Các ứng dụng web ngày càng được cá nhân hóa. Ngoài ra, có sự gia tăng của các ứng dụng web sử dụng các dịch vụ dựa trên vị trí vì nhiều lý do. Nó có thể dành cho quyền riêng tư, bảo mật hoặc bất kỳ tính năng dựa trên vị trí cụ thể nào.


Khi biết vị trí của người dùng, bạn có thể dễ dàng cung cấp các tính năng khác nhau như nội dung được cá nhân hóa, bảo mật tốt , dự báo thời tiết, bản địa hóa nhiều hơn và trải nghiệm người dùng tốt hơn. Là một nhà phát triển, điều quan trọng là phải biết cách lấy vị trí của người dùng cho các ứng dụng web của bạn. Trong bài viết này, chúng tôi sẽ hướng dẫn bạn từng bước về cách bạn có thể lấy vị trí của người dùng bằng các công nghệ web hiện đại như React.js . Chúng tôi sẽ đề cập đến các phương pháp khác nhau để lấy địa chỉ IP của người dùng và cách chúng tôi có thể nhận thêm dữ liệu định vị địa lý từ người dùng. Vậy hãy bắt đầu!

IP hoặc định vị địa lý là gì?

Trước tiên, hãy hiểu IP hoặc địa chỉ IP là gì.


Mỗi thiết bị sử dụng internet có một địa chỉ duy nhất giống như mỗi ngôi nhà có một số hoặc địa chỉ duy nhất. Địa chỉ duy nhất này của thiết bị được gọi là địa chỉ IP . Nó giúp các thiết bị giao tiếp với nhau qua internet.


Ở đây, IP là viết tắt của “Internet Protocol“ , đây là một bộ quy tắc mà các thiết bị sử dụng để giao tiếp với nhau qua Internet. Bây giờ hãy hiểu Định vị địa lý .


Vị trí địa lý là sự kết hợp của hai từ. Đầu tiên là “Geo” có nghĩa là trái đất và từ thứ hai là “ location” có nghĩa là vị trí của một thứ gì đó trên trái đất.


Vì vậy, “IP-Geolocation” là một cách để tìm vị trí thực tế của thiết bị dựa trên địa chỉ IP của thiết bị . Độ chính xác của dữ liệu Định vị địa lý IP phụ thuộc vào các yếu tố khác nhau, chẳng hạn như phương pháp nào được sử dụng để lấy dữ liệu, chất lượng dữ liệu, loại thiết bị, v.v. Dữ liệu có thể kém chính xác hơn đối với thiết bị di động do các thiết bị này thường xuyên thay đổi mạng. Bây giờ hãy xem cách lấy địa chỉ IP của người dùng trong React.


Lấy vị trí của người dùng

Có một số phương pháp mà chúng tôi có thể có được vị trí của người dùng. Đối với bài viết này, chúng tôi sẽ sử dụng API vị trí địa lý được hỗ trợ bởi tất cả các trình duyệt và chúng tôi không phải cài đặt bất kỳ thư viện bên thứ 3 nào khác để sử dụng nó. Để có thêm dữ liệu, chúng tôi có thể sử dụng API từ các dịch vụ của bên thứ 3 như API mã hóa địa lý .


Trước tiên, hãy tạo một ứng dụng React bằng vite . Mở CMD hoặc terminal, sau đó tìm thư mục dự án của bạn và sử dụng các lệnh sau.


  • npm create vite@latest your-app-name


Khi bạn thực hiện lệnh trên, nó sẽ hiển thị cho bạn một danh sách các khung, bây giờ hãy chọn Phản ứng từ danh sách bằng các phím mũi tên và nhấn enter.


  • Bây giờ nó sẽ hiển thị cho bạn một danh sách các biến thể, hãy chọn Javascript từ các biến thể đó. Sau đó, nó sẽ tạo một thư mục có tên ứng dụng của bạn và thêm tất cả các tệp cần thiết. Bây giờ sử dụng các lệnh sau để cài đặt tất cả các thư viện cần thiết và chạy máy chủ phát triển.


    • cd your-app-name Lệnh này sẽ thay đổi thư mục hiện tại thành thư mục ứng dụng
    • npm install Sử dụng lệnh này, chúng tôi sẽ cài đặt tất cả các thư viện cần thiết
    • npm run dev Lệnh này sẽ khởi động máy chủ phát triển tại localhost:5173


Bây giờ máy chủ phát triển đã hoạt động, vì vậy hãy lấy địa chỉ IP của người dùng.

Nhận thông tin kinh độ và vĩ độ của người dùng bằng cách sử dụng API vị trí địa lý

API vị trí địa lý cho phép người dùng cung cấp vị trí của họ nếu họ muốn. Phương pháp này chỉ hoạt động nếu người dùng cấp quyền truy cập vị trí của mình, nếu không thì API dựa trên trình duyệt này sẽ không thể lấy địa chỉ IP. Nếu tính năng trên ứng dụng web của bạn thực sự yêu cầu vị trí của người dùng thì bạn có thể nhắc người dùng bằng một tin nhắn văn bản hay và cho họ biết lý do bạn yêu cầu vị trí của họ.


API định vị địa lý này được truy cập thông qua một lệnh gọi đến navigator.geolocation , đây là một thuộc tính có thể trả về một đối tượng Định vị địa lý. Hãy xem navigator là gì.


  • navigator: Đây là một giao diện đại diện cho trạng thái và danh tính của tác nhân người dùng (trình duyệt). Hãy đăng nhập trình điều hướng này và xem những thuộc tính khác mà nó cung cấp.


    Mở tệp App.jsx và làm sạch tất cả mã bên trong câu lệnh trả về và chỉ giữ lại div cha với lớp App , sau đó sử dụng console.log(navigator) bên trong useEffect như được đưa ra trong đoạn mã sau.


 import { useEffect } from "react"; import "./App.css"; function App() { useEffect(() => { console.log(navigator); }, []); return <div className="App"></div>; } export default App;


Trong tab máy chủ phát triển của bạn, hãy mở bảng điều khiển bằng cách nhấn F12 hoặc nhấp chuột phải. Bạn sẽ thấy danh sách các thuộc tính khác nhau như appCodeName , Bluetooth , clipboard , định vị địa lý, v.v. Hãy ghi lại vị trí địa lý. Chỉ cần thêm vị trí địa lý trong nhật ký như thế này console.log(navigator.geolocation) . Trong bảng điều khiển, bạn sẽ thấy một đối tượng trống vì hiện tại chúng tôi không có quyền truy cập vào vị trí địa lý, vì vậy hãy xem cách nhận quyền này. Kiểm tra mã sau đây và giải thích.


 useEffect(() => { if (navigator.geolocation) { navigator.permissions .query({ name: "geolocation" }) .then(function (result) { console.log(result); }); } else { console.log("Geolocation is not supported by this browser."); } }, []);


Trong useEffect ở trên, đầu tiên chúng ta sẽ kiểm tra xem có navigator hay không. định vị địa lý hay không và nếu đó là sự thật, chúng tôi sẽ kiểm tra các quyền.


Đây,

  • navigator.permissions: Nó trả về một đối tượng Quyền có thể được sử dụng để truy vấn và cập nhật trạng thái quyền.

  • truy vấn: Đây là một phương thức Quyền trả về trạng thái của quyền người dùng. Nó nhận một đối tượng có danh sách các cặp tên-giá trị được phân tách bằng dấu phẩy. Ở đây chúng tôi đã vượt qua định vị địa lý vì chúng tôi muốn biết trạng thái của quyền định vị địa lý.


    Ở đây chúng tôi đang nhận được một Lời hứa, đó là lý do tại sao chúng tôi sẽ sử dụng từ khóa then và ghi lại kết quả. Nếu bạn kiểm tra bảng điều khiển của trình duyệt, bạn sẽ thấy đối tượng sau.

     { name: "geolocation", onchange: null, state: "prompt" }


Trạng thái có thể có 3 giá trị khác nhau,


  • được cấp: Điều đó có nghĩa là chúng tôi có quyền truy cập vị trí để chúng tôi có thể gọi vị trí địa lý trực tiếp.
  • lời nhắc: Người dùng sẽ nhận được một cửa sổ bật lên yêu cầu quyền.
  • bị từ chối: Điều đó có nghĩa là người dùng đã từ chối chia sẻ vị trí của mình.


Đối với trạng thái “được cấp”“nhắc nhở”, chúng tôi có thể tạo một chức năng để lấy vị trí hiện tại của người dùng nhưng đối với trạng thái “ bị từ chối ”, chúng tôi có thể hiển thị hướng dẫn cách họ có thể bật quyền truy cập vị trí trong trình duyệt của mình.


LƯU Ý: Không có cách nào bạn có thể nhắc lại quyền vị trí nếu người dùng đã từ chối quyền vị trí trừ khi người dùng bật nó theo cách thủ công trong trình duyệt của mình.


Hãy sử dụng 3 điều kiện cho cả 3 trạng thái quyền như sau


 useEffect(() => { if (navigator.geolocation) { navigator.permissions .query({ name: "geolocation" }) .then(function (result) { console.log(result); if (result.state === "granted") { //If granted then you can directly call your function here } else if (result.state === "prompt") { //If prompt then the user will be asked to give permission } else if (result.state === "denied") { //If denied then you have to show instructions to enable location } }); } else { console.log("Geolocation is not supported by this browser."); } }, []);


Ở trạng thái prompt , hãy sử dụng hàm có tên là getCurrentPosition . Hàm này nhận các đối số sau.


  • thành công : Hàm gọi lại sẽ được gọi nếu vị trí được truy xuất thành công.
  • lỗi : Hàm gọi lại sẽ được gọi nếu có lỗi khi truy xuất vị trí. Đây là tùy chọn.
  • tùy chọn : Các tùy chọn có các tham số khác nhau như độ tuổi tối đa, thời gian chờ, enableHighAccuracy, v.v. Bạn có thể đọc thêm về các tùy chọn này từ tài liệu MDN . Đây cũng là tùy chọn.


Hãy triển khai cả 3 đối số. Trước tiên, hãy tạo một chức năng thành công . Trước useEffect thêm chức năng thành công sau.

 function success(pos) { var crd = pos.coords; console.log("Your current position is:"); console.log(`Latitude : ${crd.latitude}`); console.log(`Longitude: ${crd.longitude}`); console.log(`More or less ${crd.accuracy} meters.`); }


Hàm gọi lại này sẽ trả về một đối tượng vị trí có coords có nghĩa là tọa độ. Bạn có thể nhận các giá trị như vĩ độ , kinh độ , độ chính xác , v.v. từ tọa độ. Bây giờ thêm chức năng lỗi sau sau chức năng thành công.

 function errors(err) { console.warn(`ERROR(${err.code}): ${err.message}`); }


Hãy khai báo một đối tượng tùy chọn ,

 var options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, };


Bây giờ, hãy dán dòng sau vào các điều kiện “nhắc nhở”“được cấp” và kiểm tra đầu ra trong trình duyệt của bạn.

 navigator.geolocation.getCurrentPosition(success, errors, options)


Bạn có thể thấy một lời nhắc trong trình duyệt yêu cầu bạn cấp quyền vị trí. Khi bạn cấp quyền, bạn có thể thấy các giá trị tọa độ trong tab bảng điều khiển. Đây là mã đầy đủ.

 import { useEffect } from "react"; import "./App.css"; function App() { var options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }; function success(pos) { var crd = pos.coords; console.log("Your current position is:"); console.log(`Latitude : ${crd.latitude}`); console.log(`Longitude: ${crd.longitude}`); console.log(`More or less ${crd.accuracy} meters.`); } function errors(err) { console.warn(`ERROR(${err.code}): ${err.message}`); } useEffect(() => { if (navigator.geolocation) { navigator.permissions .query({ name: "geolocation" }) .then(function (result) { console.log(result); if (result.state === "granted") { //If granted then you can directly call your function here navigator.geolocation.getCurrentPosition(success, errors, options); } else if (result.state === "prompt") { //If prompt then the user will be asked to give permission navigator.geolocation.getCurrentPosition(success, errors, options); } else if (result.state === "denied") { //If denied then you have to show instructions to enable location } }); } else { console.log("Geolocation is not supported by this browser."); } }, []); return <div className="App"></div>; } export default App;


Đó là nó. Đây là cách bạn có thể dễ dàng nhận được giá trị kinh độ và vĩ độ của bất kỳ thiết bị nào. Bây giờ thông tin này là không đủ, vì vậy hãy sử dụng API mã hóa địa lý ngược của opencage để có thêm thông tin về vị trí của người dùng.


Tăng cường các dịch vụ dựa trên vị trí với Mã hóa địa lý ngược

Chúng tôi đã có thông tin kinh độ và vĩ độ về thiết bị của người dùng, bây giờ hãy xem cách chúng tôi có thể sử dụng thông tin đó để nhận thêm thông tin về vị trí của thiết bị. Chúng tôi sẽ sử dụng API từ opencage để có thêm thông tin về vị trí. Vì vậy, hãy đảm bảo bạn đăng ký và nhận khóa API. Bạn không phải trả tiền cho bất cứ thứ gì vì opencage cung cấp 2.500 yêu cầu/ngày trong bản dùng thử miễn phí. Bạn có thể kiểm tra thêm thông tin về mẫu giá trên trang web của họ. Tôi đã sử dụng API của Opencage vì chúng có bản dùng thử miễn phí và cũng không có giới hạn CORS. Bây giờ hãy tạo một hàm để sử dụng API này.


 const [location, setLocation] = useState(); function getLocationInfo(latitude, longitude) { const url = `https://api.opencagedata.com/geocode/v1/json?q=${latitude},${longitude}&key=${APIkey}`; fetch(url) .then((response) => response.json()) .then((data) => { console.log(data); if (data.status.code === 200) { console.log("results:", data.results); setLocation(data.results[0].formatted); } else { console.log("Reverse geolocation request failed."); } }) .catch((error) => console.error(error)); }


Hàm getLocationInfo này lấy vĩ độ và kinh độ, sau đó nó sử dụng URL từ tài liệu của opencage lấy vĩ độ, kinh độ và khóa API. Trong chức năng này, chúng tôi đã sử dụng tìm nạp để lấy dữ liệu từ URL và nếu mã trạng thái là 200 thì chúng tôi sẽ lưu trữ dữ liệu ở trạng thái vị trí . Bạn có thể kiểm tra bảng điều khiển của mình trên trình duyệt để xem loại dữ liệu bạn đang nhận. Hiện tại, chúng tôi đang sử dụng giá trị từ thuộc tính được định dạng . Để biết thêm thông tin về đối tượng phản hồi , bạn có thể kiểm tra tài liệu. Bây giờ, hãy gọi hàm này từ hàm thành công mà chúng ta đã xác định.


 function success(pos) { var crd = pos.coords; ... ... getLocationInfo(crd.latitude, crd.longitude); }


Bây giờ, bất cứ khi nào người dùng đồng ý cung cấp vị trí của mình, hàm thành công sẽ chạy và nó cũng sẽ chuyển thông tin kinh độ và vĩ độ cho hàm getLocationInfo và thực thi nó. Bạn có thể sử dụng trạng thái vị trí này để hiển thị vị trí trong ứng dụng web của mình.


Đây là mã đầy đủ:

 import { useEffect, useState } from "react"; import "./App.css"; const APIkey = "Enter-your-api-key"; function App() { const [location, setLocation] = useState(); function getLocationInfo(latitude, longitude) { const url = `https://api.opencagedata.com/geocode/v1/json?q=${latitude},${longitude}&key=${APIkey}`; fetch(url) .then((response) => response.json()) .then((data) => { console.log(data); if (data.status.code === 200) { console.log("results:", data.results); setLocation(data.results[0].formatted); } else { console.log("Reverse geolocation request failed."); } }) .catch((error) => console.error(error)); } var options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }; function success(pos) { var crd = pos.coords; console.log("Your current position is:"); console.log(`Latitude : ${crd.latitude}`); console.log(`Longitude: ${crd.longitude}`); console.log(`More or less ${crd.accuracy} meters.`); getLocationInfo(crd.latitude, crd.longitude); } function errors(err) { console.warn(`ERROR(${err.code}): ${err.message}`); } useEffect(() => { if (navigator.geolocation) { navigator.permissions .query({ name: "geolocation" }) .then(function (result) { console.log(result); if (result.state === "granted") { //If granted then you can directly call your function here navigator.geolocation.getCurrentPosition(success, errors, options); } else if (result.state === "prompt") { //If prompt then the user will be asked to give permission navigator.geolocation.getCurrentPosition(success, errors, options); } else if (result.state === "denied") { //If denied then you have to show instructions to enable location } }); } else { console.log("Geolocation is not supported by this browser."); } }, []); return ( <div className="App"> {location ? <>Your location: {location}</> : null} </div> ); } export default App;


Bạn cũng có thể triển khai trạng thái lỗi hiển thị thông báo lỗi khi API hoặc chức năng không thể tìm nạp dữ liệu đúng cách. Cũng thực hiện phương pháp xử lý lỗi cần thiết trên các chức năng. Đây là cách bạn có thể nhận được nhiều thông tin về vị trí của thiết bị. Bây giờ hãy xem cách bạn có thể lấy địa chỉ IP của thiết bị.


Lấy địa chỉ IP của người dùng

Để lấy địa chỉ IP của người dùng ở phía máy khách, chúng tôi sẽ sử dụng ipify , một API địa chỉ IP công khai đơn giản. Chúng tôi sẽ tạo một useEffect khác và tạo một chức năng để lấy địa chỉ IP như được hiển thị trong khối mã sau.


 const [ipAddress, setIpAddress] = useState(''); useEffect(() => { const fetchIp = async () => { try { const response = await fetch('https://api.ipify.org?format=json'); const data = await response.json(); setIpAddress(data.ip); } catch (error) { console.error(error); } }; fetchIp(); }, []);


Trong đoạn mã trên, chúng tôi đã tạo một trạng thái gọi là ipAddress để lưu trữ địa chỉ IP. Ipfy là một API công khai, vì vậy chúng tôi không cần bất kỳ khóa API nào và chúng tôi có thể trực tiếp sử dụng URL. Trong useEffect chúng tôi đã tạo một hàm không đồng bộ có tên là fetchIp , hàm này sẽ tìm nạp và lưu trữ địa chỉ IP ở trạng thái ipAddress . Đừng quên gọi hàm fetchIp trong useEffect .


Đây là cách bạn có thể dễ dàng lấy địa chỉ IP của thiết bị. Có nhiều API cung cấp các dịch vụ như vậy. Đối với phía máy chủ, bạn thường có thể lấy địa chỉ IP từ yêu cầu mà người dùng đã gửi cho bạn để lấy trang web hoặc bất kỳ dữ liệu nào. Tuy nhiên, bạn không nên chỉ dựa vào địa chỉ IP vì một số ISP sử dụng địa chỉ IP dùng chung, nghĩa là nhiều người dùng có thể có cùng một địa chỉ IP. Ngoài ra, một số người dùng cũng sử dụng VPN và proxy.


Các phương pháp hay nhất để thu thập và xử lý dữ liệu vị trí của người dùng

Khi nói đến dữ liệu của người dùng, nó luôn là một phần quan trọng trong bất kỳ ứng dụng nào. Là một nhà phát triển, nhiệm vụ của chúng tôi là tạo ra một ứng dụng lấy người dùng làm trung tâm và bảo vệ dữ liệu của người dùng. Đối với dữ liệu như vị trí của thiết bị, nhà phát triển có thể gặp phải nhiều vấn đề như,


  • Dữ liệu IP hoặc Định vị địa lý chính xác đến mức nào?
  • Phải làm gì khi người dùng không đồng ý sử dụng vị trí của nó?
  • Làm thế nào để xử lý và bảo vệ dữ liệu an toàn?


Việc triển khai các tính năng dựa trên vị trí có thể là một thách thức, nhưng bằng cách làm theo các phương pháp hay nhất, bạn có thể cung cấp cho người dùng trải nghiệm cá nhân hóa và an toàn hơn. Hãy ghi nhớ các điểm sau khi xử lý dữ liệu dựa trên vị trí.


  • Độ chính xác: Khi sử dụng địa chỉ IP, bạn nên kiểm tra kỹ địa chỉ đó bằng cách sử dụng nhiều api và kiểm tra chéo vị trí. Bạn phải bao gồm một cơ chế dự phòng trong các dịch vụ và tính năng dựa trên vị trí. Tốt hơn là xác minh dữ liệu vì dữ liệu vị trí có thể bị ảnh hưởng bởi các yếu tố như cài đặt thiết bị, kết nối mạng, v.v.


  • Quyền riêng tư: Đây là mối quan tâm chính khi thu thập dữ liệu của bất kỳ người dùng nào vì bạn phải kiểm tra các luật khác nhau của nhiều quốc gia. Các nhà phát triển phải luôn tôn trọng quyền riêng tư của người dùng. Cần phải có một chính sách bảo mật rõ ràng và ngắn gọn khi thu thập bất kỳ dữ liệu nào từ người dùng. Bạn phải thông báo cho người dùng bất cứ khi nào bạn thu thập bất kỳ thông tin nhạy cảm nào. Nếu có bất kỳ tính năng dựa trên vị trí nào thì bạn nên trình bày rõ ràng lý do và cách bạn sẽ sử dụng dữ liệu. Ngoài ra, bạn nên cung cấp tùy chọn từ chối cho người dùng để nếu họ không muốn chia sẻ dữ liệu của mình, họ có thể từ chối dễ dàng.


  • Bảo mật: Để bảo mật dữ liệu, bạn có thể triển khai các cơ chế xác thực như OAuth hoặc JWT và bảo mật các điểm cuối API khỏi mọi truy cập trái phép. Bạn phải luôn đề xuất người dùng sử dụng mật khẩu mạnh, cập nhật trình duyệt, v.v. Bạn phải mã hóa dữ liệu bằng chứng chỉ HTTPS và SSL để đảm bảo liên lạc an toàn giữa máy khách và máy chủ. Trong phần phụ trợ, bạn có thể triển khai kiểm soát truy cập dựa trên vai trò để chỉ người dùng được chọn, chẳng hạn như chỉ quản trị viên mới có thể truy cập dữ liệu. Trước khi cập nhật bất kỳ phần phụ thuộc nào, bạn phải luôn kiểm tra xem nó có phải từ nguồn chính thức hay không.


    Phần kết luận

    Dữ liệu vị trí đóng một vai trò quan trọng khi nói đến trải nghiệm được cá nhân hóa của người dùng, tuy nhiên, việc xử lý dữ liệu này cũng là một nhiệm vụ quan trọng. Có nhiều phương pháp khác nhau mà bạn có thể sử dụng để lấy dữ liệu vị trí nhưng nó phụ thuộc vào lượng dữ liệu bạn cần nếu bạn chỉ cần một địa chỉ IP thì bạn có thể sử dụng API ipify và nếu bạn cần thêm dữ liệu định vị địa lý thì bạn có thể sử dụng một trong hai opencage hoặc API mã hóa địa lý ngược của google . Nếu bạn đang xây dựng các ứng dụng nhỏ chẳng hạn như ứng dụng thời tiết thì bạn không cần sử dụng bất kỳ API nào vì bạn có thể sử dụng API định vị địa lý của trình duyệt. Khi nói đến tính bảo mật và quyền riêng tư của những dữ liệu này, bạn nên tránh mọi rủi ro tiềm ẩn liên quan đến quyền riêng tư của người dùng bằng cách cung cấp chính sách quyền riêng tư được viết rõ ràng. Đó là kết thúc của bài viết này hy vọng bạn thích nó.


    • Kiểm tra trang web của tôi được gọi là DevDreamning .
    • Kênh youtube của tôi👇😉