لو بتفكر تحول موقعك لـ PWA، غالبًا أول حاجة هتحتاجها مش الـ offline mode ولا الـ push notifications، دي حاجات بتيجي بعدين. أول حاجة الناس بتسألني عليها هي: إزاي أوصل لكاميرا الموبايل وموقع المستخدم من جوه المتصفح؟ في المقال ده هنعدي على الاتنين بالتفصيل، وهنشوف كمان الفروق اللي هتقابلك بين Android وiOS، لأن دي النقطة اللي بتضيع ناس كتير وقت في تصحيحها.
المحتويات
- أولاً: إيه هي الـ PWA وليه محتاجها أصلاً
- ثانياً: الوصول للكاميرا باستخدام getUserMedia
- ثالثاً: تحديد الموقع الجغرافي مع Geolocation API
- رابعاً: الفرق بين Android وiOS في التعامل مع الصلاحيات
- خامساً: أخطاء شائعة هتقابلك وإزاي تتعامل معاها
- سادساً: خطوة سريعة قبل النشر: الـ Manifest والـ HTTPS
- سابعاً: مثال عملي: صفحة توثيق موقع بالكاميرا والموقع الجغرافي مع بعض
أولاً: إيه هي الـ PWA وليه محتاجها أصلاً
الـ Progressive Web App هي موقع ويب عادي، لكنه مكتوب وبيتصرف بشكل قريب جدًا من تطبيق موبايل حقيقي. المستخدم يقدر يضيفه على الشاشة الرئيسية، يفتحه بدون شريط المتصفح، وياخد صلاحيات زي الكاميرا والموقع نفس أي تطبيق تقريبًا. الفايدة الأساسية إنك مش محتاج تكتب تطبيق منفصل لكل من Android وiOS، بتكتب كود واحد ويشتغل في المتصفح على الاتنين.
المشكلة إن المتصفح مش زي بيئة التطبيق الأصلي (Native)، فيه قيود على الصلاحيات، والسلوك بيختلف من متصفح للتاني ومن نظام تشغيل للتاني. عشان كده لازم تفهم الـ APIs دي كويس قبل ما تعتمد عليها في مشروع حقيقي.
ثانياً: الوصول للكاميرا باستخدام getUserMedia
الطريقة الرسمية للوصول للكاميرا من المتصفح هي navigator.mediaDevices.getUserMedia(). الدالة دي بترجع Promise، ولو المستخدم وافق على الصلاحية بيرجعلك Stream تقدر تربطه بعنصر video في الصفحة.
async function openCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment" },
audio: false
});
const videoEl = document.querySelector("#camera-preview");
videoEl.srcObject = stream;
} catch (err) {
console.error("مش قادر أفتح الكاميرا:", err);
}
}
لاحظ الخيار facingMode: “environment”، ده اللي بيخليك تفتح الكاميرا الخلفية بدل الأمامية على الموبايل، مهم جدًا لو عملت وظيفة مسح باركود أو تصوير مستندات مثلًا. لما تخلص من الكاميرا لازم توقف الـ tracks يدويًا، وإلا هتلاقي أيقونة الكاميرا شغالة في المتصفح حتى بعد ما تسيب الصفحة:
function closeCamera(stream) {
stream.getTracks().forEach(track => track.stop());
}
لو عايز تفهم فكرة الـ async/await والـ Promises بشكل أعمق، أنا شرحتهم بالتفصيل في كورس الـ JavaScript اللي هيفيدك جدًا هنا.
ثالثاً: تحديد الموقع الجغرافي مع Geolocation API
الـ Geolocation API أبسط شوية من الكاميرا. بتستخدم navigator.geolocation.getCurrentPosition() لو محتاج قراءة واحدة، أو watchPosition() لو عايز تتابع الموقع وهو بيتحرك، زي تطبيق توصيل مثلًا.
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
console.log(latitude, longitude);
},
(error) => {
console.error("مش قادر أحدد الموقع:", error.message);
},
{ enableHighAccuracy: true, timeout: 8000 }
);
الخيار enableHighAccuracy بيطلب من الجهاز يستخدم GPS بدل الاعتماد على الشبكة بس، الدقة بتزيد بس الاستهلاك للبطارية بيزيد معاها، فاستخدمه بس لو فعلًا محتاج دقة عالية زي تطبيقات الملاحة.
رابعاً: الفرق بين Android وiOS في التعامل مع الصلاحيات
هنا بيبدأ الصداع الحقيقي. على Android في Chrome، الـ APIs دي بتشتغل بشكل قريب جدًا من التطبيق الأصلي، والصلاحية بتتسجل على مستوى المتصفح وبتفضل محفوظة. أما على iOS في Safari فالوضع مختلف:
- سفاري بيطلب صلاحية الكاميرا أو الموقع في كل جلسة تصفح جديدة أحيانًا، مش زي Chrome اللي بيتذكرها لفترة أطول.
- لو الـ PWA متضافة للشاشة الرئيسية (Add to Home Screen)، الصلاحيات بتتعامل معاها iOS كأنها موقع منفصل تمامًا عن نسخة سفاري العادية، يعني ممكن تطلب الإذن مرة تانية حتى لو المستخدم وافق قبل كده من المتصفح.
- الـ getUserMedia مش هيشتغل خالص على iOS لو الصفحة مش على HTTPS، وده فرق مهم عن بعض المتصفحات على Android اللي بتسمح بيه على localhost وقت التطوير.
- دعم الـ background location على iOS محدود جدًا مقارنة بـ Android، فلو مشروعك محتاج تتبع الموقع في الخلفية، الـ PWA هتقابل حائط سد على iPhone وهتحتاج تفكر في حل تاني.
يعني عمليًا: اختبر مشروعك على جهاز iPhone حقيقي مبكرًا، ومتعتمدش إن اللي شغال على Android هيشتغل بنفس الشكل هناك.
خامساً: أخطاء شائعة هتقابلك وإزاي تتعامل معاها
أكتر خطأ بيتكرر هو إن المطور بيفترض إن الصلاحية اتوافق عليها من غير ما يتأكد، والمستخدم لما يرفض الإذن التطبيق بيقف من غير رسالة واضحة. لازم تتعامل مع الرفض كحالة طبيعية، مش استثناء:
navigator.permissions.query({ name: "geolocation" }).then((result) => {
if (result.state === "denied") {
// اعرض رسالة واضحة للمستخدم واشرحله يفعّل الصلاحية من إعدادات المتصفح
}
});
خطأ تاني شائع هو نسيان تنظيف الـ stream بعد إغلاق الكاميرا، زي ما ذكرت فوق. وخطأ ثالث هو الاعتماد على HTTP وقت التجربة على موبايل حقيقي، وده هيمنع الوصول للكاميرا والموقع تمامًا على أغلب المتصفحات الحديثة، استخدم أدوات زي ngrok أو Vercel عشان تجرب على HTTPS من أول يوم.
سادساً: خطوة سريعة قبل النشر: الـ Manifest والـ HTTPS
عشان المتصفح يعتبر موقعك PWA فعلية ويسمح بإضافته للشاشة الرئيسية، محتاج ملف manifest.json بسيط فيه اسم التطبيق والأيقونات والألوان، بالإضافة لـ Service Worker ولو بسيط. لو مشروعك بـ React، خطوة النشر نفسها شرحتها بالتفصيل في مقال منفصل، ونصيحتي تراجعه قبل ما تنشر نسخة الـ PWA بتاعتك.
ولو لسه بتبني أساسيات مشروعك على GitHub وعايز تنظم الفروع والـ commits بتاعتك كويس قبل ما توصل لمرحلة النشر، عندي دليل كامل عن استخدام GitHub كأداة تعلم يساعدك في الجزء ده.
سابعاً: مثال عملي: صفحة توثيق موقع بالكاميرا والموقع الجغرافي مع بعض
لتوضيح إزاي الاتنين بيشتغلوا مع بعض في الواقع، تخيل صفحة بسيطة بتخلي المستخدم يصور مستند ويربطه تلقائيًا بموقعه الجغرافي، زي تطبيقات توثيق الحوادث أو تسليم الطلبات. الفكرة إنك تجمع بيانات الكاميرا والموقع في نفس الوقت وترسلهم مع بعض للسيرفر.
async function captureWithLocation() {
// الخطوة الأولى: نجيب الموقع الجغرافي أولاً
const position = await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
// الخطوة الثانية: نفتح الكاميرا ونلتقط صورة
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const track = stream.getVideoTracks()[0];
const capturer = new ImageCapture(track);
const photoBlob = await capturer.takePhoto();
track.stop();
// الخطوة التالتة: نجهز البيانات ونبعتها للسيرفر
const formData = new FormData();
formData.append("photo", photoBlob);
formData.append("lat", position.coords.latitude);
formData.append("lng", position.coords.longitude);
await fetch("/api/upload", { method: "POST", body: formData });
}
لاحظ إننا طلبنا الموقع الجغرافي الأول قبل فتح الكاميرا، مش عشوائي. لو الاتنين اتطلبوا في نفس اللحظة، المستخدم هياخد صلاحيتين طالبين مرة واحدة وده بيربكه وبيزود احتمال الرفض. اطلب الصلاحيات على مراحل، وكل صلاحية في اللحظة اللي فعلًا محتاجها فيها.
حاجة تانية مهمة: ImageCapture API لسه دعمه في Safari محدود مقارنة بـ Chrome، فلو الجمهور بتاعك فيه نسبة كبيرة من مستخدمي iPhone، غالبًا هتحتاج تعتمد بدل منه على التقاط فريم من الـ video element نفسه عن طريق canvas، وده موضوع يستاهل مقال منفصل لوحده.
الـ PWA بشكل عام جزء من موجة أكبر شايفينها في اتجاهات البرمجة الحديثة، لو حابب تاخد نظرة أوسع على الموضوع اقرا مقال اتجاهات البرمجة الحديثة.
الكاميرا والموقع الجغرافي بس نقطة البداية. لو عندك تجربة سابقة مع PWA وقابلتك مشكلة تانية غير اللي ذكرتها هنا، قولّي عليها في التعليقات، ممكن تبقى موضوع مقال جاي.
اكتشاف المزيد من كود التطور
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.


