تشفير حمى المقصورة: برنامج تعليمي للجهة الخلفية لـ Node.js
نشرت: 2022-03-11لقد أدى إغلاق COVID-19 إلى توقف الكثير منا في المنزل ، ربما على أمل أن مجرد حمى المقصورة هو أسوأ أنواع الحمى التي سنواجهها. يستهلك الكثير منا محتوى فيديو أكثر من أي وقت مضى. في حين أن التمرين مهم بشكل خاص في الوقت الحالي ، في بعض الأحيان ، هناك حنين إلى رفاهية جهاز تحكم عن بعد قديم الطراز عندما يكون الكمبيوتر المحمول بعيدًا عن متناول اليد.
هذا هو المكان الذي يأتي فيه هذا المشروع: فرصة تحويل أي هاتف ذكي - حتى الهاتف القديم الذي يكون عديم الفائدة بخلاف ذلك بسبب نقص التحديثات - إلى جهاز تحكم عن بعد مفيد لـ Netflix / YouTube / Amazon Prime Video التالي / إلخ. مشاهدة حفلة. إنه أيضًا برنامج تعليمي للجهة الخلفية Node.js: فرصة لتعلم أساسيات JavaScript الخلفية باستخدام إطار عمل Express ومحرك نموذج Pug (المعروف سابقًا باسم Jade).
إذا كان هذا يبدو شاقًا ، فسيتم تقديم مشروع Node.js الكامل في النهاية ؛ يحتاج القراء فقط إلى التعلم بقدر اهتمامهم بالتعلم ، وسيكون هناك عدد لا بأس به من التفسيرات اللطيفة لبعض الأساسيات على طول الطريق التي يمكن للقراء الأكثر خبرة تخطيها.
لماذا ليس فقط ...؟
قد يتساءل القراء ، "لماذا تدخل في ترميز النهاية الخلفية لـ Node.js؟" (بصرف النظر عن فرصة التعلم بالطبع). "ألا يوجد بالفعل تطبيق لذلك؟"
بالتأكيد - الكثير منهم. ولكن هناك سببان رئيسيان قد يكون هذا غير مرغوب فيه:
- بالنسبة لأولئك الذين يحاولون إعادة استخدام هاتف قديم ، فقد لا يكون هذا خيارًا بعد الآن ، كما هو الحال مع جهاز Windows Phone 8.1 الذي أردت استخدامه. (تم إغلاق متجر التطبيقات رسميًا في أواخر عام 2019.)
- الثقة (أو عدم الثقة). مثل العديد من التطبيقات التي يمكن العثور عليها على أي نظام أساسي للجوّال ، غالبًا ما تأتي مع متطلبات منح المستخدمين أذونات أكثر بكثير مما يحتاجه التطبيق لما يفترض القيام به. ولكن حتى إذا كان هذا الجانب محدودًا بشكل مناسب ، فإن طبيعة تطبيق التحكم عن بُعد تعني أنه لا يزال يتعين على المستخدمين الوثوق في أن مطوري التطبيقات لا يسيئون استخدام امتيازاتهم في نهاية الحل على سطح المكتب من خلال تضمين برامج التجسس أو البرامج الضارة الأخرى.
كانت هذه المشكلات موجودة منذ وقت طويل وكانت حتى الدافع لمشروع مماثل من 2014 موجود على GitHub. يجعل nvm
تثبيت الإصدارات القديمة من Node.js أمرًا سهلاً ، وحتى إذا احتاجت بعض التبعيات إلى الترقية ، فإن Node.js يتمتع بسمعة طيبة لكونه متوافقًا مع الإصدارات السابقة.
لسوء الحظ ، فاز bitrot. لم يكن النهج العنيد والتوافق الخلفي لـ Node.js متطابقين مع الإهمالات اللانهائية وحلقات التبعية المستحيلة بين الإصدارات القديمة من Grunt و Bower وعشرات المكونات الأخرى. بعد ساعات ، كان من الواضح أنه سيكون من الأسهل بكثير البدء من الصفر - على الرغم من نصيحة هذا المؤلف ضد إعادة اختراع العجلة.
أدوات جديدة من قديم: إعادة استخدام الهواتف كوحدات تحكم عن بعد باستخدام الطرف الخلفي Node.js
أولاً ، لاحظ أن مشروع Node.js هذا مخصص حاليًا لنظام Linux - تم تطويره واختباره على Linux Mint 19 و Linux Mint 19.3 ، على وجه الخصوص - ولكن يمكن بالتأكيد إضافة دعم للأنظمة الأساسية الأخرى. قد يعمل بالفعل على جهاز Mac.
بافتراض تثبيت إصدار حديث من Node.js ، وفتح موجه الأوامر في دليل جديد سيكون بمثابة جذر المشروع ، فنحن على استعداد للبدء في Express:
npx express-generator --view=pug
ملاحظة: هنا ، npx
هي أداة مفيدة تأتي مع npm
، مدير الحزم Node.js الذي يأتي مع Node.js. نحن نستخدمه لتشغيل منشئ الهيكل العظمي لتطبيق Express. حتى كتابة هذه السطور ، يقوم المولد بإنشاء مشروع Express / Node.js والذي ، بشكل افتراضي ، لا يزال يسحب محرك قالب يسمى Jade ، على الرغم من أن مشروع Jade أعاد تسمية نفسه إلى "Pug" من الإصدار 2.0 وما بعده. لذا ، لكي تكون محدثًا وتستخدم Pug على الفور - بالإضافة إلى ذلك ، تجنب تحذيرات الإهمال - نتعامل مع --view=pug
، وهو خيار سطر أوامر للنص express-generator
يتم تشغيله بواسطة npx
.
بمجرد الانتهاء من ذلك ، نحتاج إلى تثبيت بعض الحزم من قائمة التبعية التي تم ملؤها حديثًا لمشروع Node.js في package.json
. الطريقة التقليدية للقيام بذلك هي تشغيل npm i
( i
لـ "install"). لكن البعض لا يزال يفضل سرعة الغزل ، لذلك إذا كان لديك ذلك مثبتًا ، فما عليك سوى تشغيل yarn
بدون أي معلمات.
في هذه الحالة ، يجب أن يكون من الآمن تجاهل تحذير الإيقاف (الذي نأمل أن يتم إصلاحه قريبًا) من أحد تبعيات Pug الفرعية ، طالما يتم الاحتفاظ بالوصول إلى أساس ما هو مطلوب على الشبكة المحلية.
بداية سريعة yarn start
npm start
، متبوعة بالانتقال إلى localhost:3000
في مستعرض ، يوضح أن الواجهة الخلفية Node.js الأساسية المستندة إلى Express تعمل. يمكننا قتله باستخدام Ctrl+C
البرنامج التعليمي للنهاية الخلفية لـ Node.js ، الخطوة 2: كيفية إرسال ضغطات المفاتيح على الجهاز المضيف
مع اكتمال الجزء البعيد في منتصف الطريق بالفعل ، دعنا نوجه انتباهنا إلى جزء التحكم . نحتاج إلى شيء يمكنه التحكم برمجيًا في الجهاز ، وسنقوم بتشغيل النهاية الخلفية لـ Node.js ، متظاهرين أنه يضغط على المفاتيح الموجودة على لوحة المفاتيح.
لذلك ، سنقوم بتثبيت xdotool
باستخدام تعليماته الرسمية. اختبار سريع لأمرهم كمثال في محطة طرفية:
xdotool search "Mozilla Firefox" windowactivate --sync key --clearmodifiers ctrl+l
… يجب أن يفعل بالضبط ما يقوله ، بافتراض أن Mozilla Firefox مفتوح في ذلك الوقت. ذلك جيد! من السهل جعل مشروعنا Node.js يستدعي أدوات سطر الأوامر مثل xdotool
، كما سنرى قريبًا.
البرنامج التعليمي للجهة الخلفية Node.js ، الخطوة 3: تصميم الميزة
قد لا يكون هذا صحيحًا بالنسبة للجميع ، ولكن شخصيًا ، أجد أن العديد من أجهزة التحكم عن بعد المادية الحديثة بها حوالي خمسة أضعاف عدد الأزرار التي سأستخدمها على الإطلاق. لذلك بالنسبة لهذا المشروع ، نحن ننظر إلى تخطيط ملء الشاشة مع شبكة من ثلاثة في ثلاثة من الأزرار الرائعة ، الكبيرة ، سهلة الاستهداف. يعود الأمر إلى التفضيل الشخصي لما قد تكون عليه هذه الأزرار التسعة.
اتضح أن اختصارات لوحة المفاتيح لأبسط الوظائف ليست متطابقة عبر Netflix و YouTube و Amazon Prime Video. ولا تعمل هذه الخدمات مع مفاتيح وسائط عامة مثل تطبيق مشغل الموسيقى الأصلي الذي من المرجح أن يفعل. أيضًا ، قد لا تتوفر وظائف معينة مع جميع الخدمات.
لذا فإن ما يتعين علينا القيام به هو تحديد تخطيط مختلف للتحكم عن بعد لكل خدمة وتوفير طريقة للتبديل بينها.
تحديد تخطيطات التحكم عن بعد وتعيينها إلى اختصارات لوحة المفاتيح
دعنا نحصل على نموذج أولي سريع يعمل مع عدد قليل من الإعدادات المسبقة. سنضعهم في common/preset_commands.js
- "عام" لأننا سنقوم بتضمين هذه البيانات من أكثر من ملف واحد:
module.exports = { // We could use ️ but some older phones (eg, Android 5.1.1) won't show it, hence ️ instead 'Netflix': { commands: { '-': 'Escape', '+': 'f', '': 'Up', '⇤': 'XF86Back', '️': 'Return', '': 'Down', '': 'Left', '': 'Right', '': 'm', }, }, 'YouTube': { commands: { '⇤': 'shift+p', '⇥': 'shift+n', '': 'Up', 'CC': 'c', '️': 'k', '': 'Down', '': 'j', '': 'l', '': 'm', }, }, 'Amazon Prime Video': { window_name_override: 'Prime Video', commands: { '⇤': 'Escape', '+': 'f', '': 'Up', 'CC': 'c', '️': 'space', '': 'Down', '': 'Left', '': 'Right', '': 'm', }, }, 'Generic / Music Player': { window_name_override: '', commands: { '⇤': 'XF86AudioPrev', '⇥': 'XF86AudioNext', '': 'XF86AudioRaiseVolume', '': 'r', '️': 'XF86AudioPlay', '': 'XF86AudioLowerVolume', '': 'Left', '': 'Right', '': 'XF86AudioMute', }, }, };
يمكن العثور على قيم رمز المفتاح باستخدام xev
. (بالنسبة لي ، لم يكن من الممكن اكتشاف "كتم الصوت" و "تشغيل الصوت" باستخدام هذه الطريقة ، لذلك استشرت أيضًا قائمة مفاتيح الوسائط.)
قد يلاحظ القراء الاختلاف في الحالة بين space
Return
- بغض النظر عن سبب ذلك ، يجب مراعاة هذه التفاصيل حتى يعمل xdotool
بشكل صحيح. فيما يتعلق بهذا ، لدينا تعريفان مكتوبان بشكل صريح - على سبيل المثال ، shift+p
على الرغم من أن P
تعمل أيضًا - فقط لإبقاء نوايانا واضحة.
البرنامج التعليمي للنهاية الخلفية لـ Node.js ، الخطوة 4: نقطة النهاية "المفتاح" لواجهة برمجة التطبيقات (Pardon the Pun)
سنحتاج إلى نقطة نهاية لـ POST
to ، والتي بدورها ستحاكي ضغطات المفاتيح باستخدام xdotool
. نظرًا لأن لدينا مجموعات مختلفة من المفاتيح يمكننا إرسالها (واحدة لكل خدمة) ، فسنقوم باستدعاء نقطة النهاية لمجموعة واحدة group
. سنعيد تعيين الغرض من نقطة نهاية users
التي تم إنشاؤها عن طريق إعادة تسمية routes/users.js
إلى routes/group.js
، وإجراء التغييرات المقابلة في app.js
:
// ... var indexRouter = require('./routes/index'); var groupRouter = require('./routes/group'); // ... app.use('/', indexRouter); app.use('/group', groupRouter); // ...
الوظيفة الأساسية هي استخدام xdotool
عبر استدعاء shell system في routes/group.js
. سنعمل على ترميز YouTube
كقائمة مفضلة في الوقت الحالي ، لأغراض الاختبار فقط.
const express = require('express'); const router = express.Router(); const debug = require('debug')('app'); const cp = require('child_process'); const preset_commands = require('../common/preset_commands'); /* POST keystroke to simulate */ router.post('/', function(req, res, next) { const keystroke_name = req.body.keystroke_name; const keystroke_code = preset_commands['YouTube'].commands[keystroke_name]; const final_command = `xdotool \ search "YouTube" \ windowactivate --sync \ key --clearmodifiers ${keystroke_code}`; debug(`Executing ${final_command}`); cp.exec(final_command, (err, stdout, stderr) => { debug(`Executed ${keystroke_name}`); return res.redirect(req.originalUrl); }); }); module.exports = router;
هنا ، نحصل على المفتاح "name" المطلوب من نص طلب POST
( req.body
) تحت المعامل المسمى keystroke_name
. سيكون هذا شيئًا مثل ️
. ثم نستخدم ذلك للبحث عن الكود المقابل من كائن commands
preset_commands['YouTube']
.
الأمر الأخير موجود في أكثر من سطر واحد ، لذا فإن \
s الموجودة في نهاية كل سطر تجمع كل القطع في أمر واحد:
-
search "YouTube"
يجلب النافذة الأولى مع "يوتيوب" في العنوان. -
windowactivate --sync
ينشط النافذة التي تم جلبها وينتظر حتى يصبح جاهزًا لتلقي ضغطة مفتاح. -
key --clearmodifiers ${keystroke_code}
يرسل ضغطات المفاتيح ، مع التأكد من محو مفاتيح التعديل مؤقتًا مثل Caps Lock التي قد تتداخل مع ما نرسله.
في هذه المرحلة ، تفترض الشفرة أننا نقوم بتزويدها بإدخال صالح - وهو أمر سنكون أكثر حرصًا بشأنه لاحقًا.
للتبسيط ، سيفترض الرمز أيضًا أن هناك نافذة تطبيق واحدة فقط مفتوحة مع "YouTube" في عنوانها - إذا كان هناك أكثر من تطابق ، فلا يوجد ضمان بأننا سنرسل ضغطات المفاتيح إلى النافذة المقصودة. إذا كانت هذه مشكلة ، فقد يساعد ذلك في تغيير عناوين النوافذ ببساطة عن طريق تبديل علامات تبويب المتصفح على جميع النوافذ إلى جانب تلك التي يتم التحكم فيها عن بُعد.
مع هذا الاستعداد ، يمكننا بدء تشغيل الخادم الخاص بنا مرة أخرى ، ولكن هذه المرة مع تمكين التصحيح حتى نتمكن من رؤية إخراج مكالمات debug
الخاصة بنا. للقيام بذلك ، ما عليك سوى تشغيل DEBUG=old-fashioned-remote:* yarn start
أو DEBUG=old-fashioned-remote:* npm start
. بمجرد تشغيله ، قم بتشغيل مقطع فيديو على YouTube ، وافتح نافذة طرفية أخرى ، وحاول إجراء مكالمة cURL:
curl --data "keystroke_name=️" http://localhost:3000/group
يرسل هذا طلب POST
مع اسم ضغط المفاتيح المطلوب في جسمه إلى الجهاز المحلي لدينا على المنفذ 3000
، المنفذ الذي تستمع إليه نهايتنا الخلفية. يجب أن يؤدي تشغيل هذا الأمر إلى إخراج ملاحظات حول Executing
والتنفيذ في نافذة Executed
، والأهم من ذلك ، npm
المتصفح وإيقاف الفيديو الخاص به مؤقتًا. يجب أن يؤدي تنفيذ هذا الأمر مرة أخرى إلى نفس الإخراج وإلغاء إيقافه مؤقتًا.
البرنامج التعليمي للجهة الخلفية Node.js ، الخطوة 5: تخطيطات متعددة للتحكم عن بعد
نهايتنا الخلفية لم تنته بعد. سنحتاجه أيضًا حتى نتمكن من:
- قم بإنشاء قائمة بتخطيطات جهاز التحكم عن بعد من
preset_commands
. - قم بإعداد قائمة من "أسماء" ضغطات المفاتيح بمجرد اختيار تخطيط معين لجهاز التحكم عن بعد. (كان من الممكن أن نختار أيضًا استخدام
common/preset_commands.js
مباشرةً في الواجهة الأمامية ، نظرًا لأنها JavaScript بالفعل ، وتمت تصفيتها هناك. هذه إحدى المزايا المحتملة للواجهة الخلفية Node.js ، نحن لا نستخدمها هنا .)
كلتا الميزتين هما المكان الذي يتقاطع فيه البرنامج التعليمي للجهة الخلفية Node.js مع الواجهة الأمامية المستندة إلى Pug التي سنبنيها.
استخدام قالب الصلصال لتقديم قائمة بوحدات التحكم عن بعد
يعني الجزء الخلفي من المعادلة تعديل routes/index.js
لتبدو كما يلي:
const express = require('express'); const router = express.Router(); const preset_commands = require('../common/preset_commands'); /* GET home page. */ router.get('/', function(req, res, next) { const group_names = Object.keys(preset_commands); res.render('index', { title: 'Which Remote?', group_names, portrait_css: `.group_bar { height: calc(100%/${Math.min(4, group_names.length)}); line-height: calc(100vh/${Math.min(4, group_names.length)}); }`, landscape_css: `.group_bar { height: calc(100%/${Math.min(2, group_names.length)}); line-height: calc(100vh/${Math.min(2, group_names.length)}); }`, }); }); module.exports = router;
هنا ، نحصل على أسماء تخطيطات التحكم عن بعد ( group_names
) من خلال استدعاء Object.keys
في ملف preset_commands
الخاص بنا. نرسلها بعد ذلك وبعض البيانات الأخرى التي سنحتاجها إلى محرك قالب Pug الذي يتم استدعاؤه تلقائيًا عبر res.render()
.
احرص على عدم الخلط بين معنى keys
هنا وضربات المفاتيح التي نرسلها: Object.keys
مصفوفة (قائمة مرتبة) تحتوي على جميع مفاتيح أزواج المفتاح والقيمة التي تشكل كائنًا في JavaScript:

const my_object = { 'a key' : 'its corresponding value' , 'another key' : 'its separate corresponding value' , };
إذا نظرنا إلى common/preset_commands.js
، فسنرى النمط أعلاه ، ومفاتيحنا (بمعنى الكائن) هي أسماء مجموعاتنا: 'Netflix'
و 'YouTube'
وما إلى ذلك. القيم المقابلة لها ليست سلاسل بسيطة كما هو مذكور أعلاه my_object
- إنها كائنات كاملة بحد ذاتها ، بمفاتيحها الخاصة ، أي commands
وربما window_name_override
.
من المسلم به أن CSS المخصص الذي يتم تمريره هنا هو نوع من الاختراق. السبب في حاجتنا إليه على الإطلاق بدلاً من استخدام حل حديث قائم على flexbox هو التوافق بشكل أفضل مع العالم الرائع لمتصفحات الهاتف المحمول في تجسيداتها القديمة الأكثر روعة. في هذه الحالة ، الشيء الرئيسي الذي يجب ملاحظته هو أنه في الوضع الأفقي ، نحتفظ بالأزرار كبيرة من خلال عرض ما لا يزيد عن خيارين لكل شاشة ؛ في الوضع الرأسي ، أربعة.
ولكن أين يتم تحويل ذلك فعليًا إلى HTML ليتم إرساله إلى المتصفح؟ هذا هو المكان الذي يأتي فيه views/index.pug
، والذي نريد أن نبدو كما يلي:
extends layout block header_injection style(media='(orientation: portrait)') #{portrait_css} style(media='(orientation: landscape)') #{landscape_css} block content each group_name in group_names span(class="group_bar") a(href='/group/?group_name=' + group_name) #{group_name}
يعتبر السطر الأول مهمًا للغاية: يعني تمديد extends layout
أن Pug سيأخذ هذا الملف في سياق views/layout.pug
، وهو نوع من القالب الأصلي سنقوم بإعادة استخدامه هنا وأيضًا في طريقة عرض أخرى. سنحتاج إلى إضافة سطرين بعد سطر link
بحيث يبدو الملف النهائي كما يلي:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') block header_injection meta(name='viewport', content='user-scalable=no') body block content
لن نتطرق إلى أساسيات HTML هنا ، ولكن بالنسبة للقراء الذين ليسوا على دراية بها ، فإن كود Pug هذا يعكس كود HTML القياسي الموجود في كل مكان تقريبًا. يبدأ الجانب القوالب منه title= title
، والذي يحدد عنوان HTML لأي قيمة تتوافق مع مفتاح title
الخاص بالكائن الذي نمرره Pug عبر res.render
.
يمكننا أن نرى جانبًا مختلفًا من تشكيل سطرين لاحقًا باستخدام block
نسميه header_injection
. كتل مثل هذه عبارة عن عناصر نائبة يمكن استبدالها بقوالب تعمل على توسيع القالب الحالي. (غير ذي صلة ، يعد خط meta
ببساطة حلًا سريعًا لمتصفحات الهاتف المحمول ، لذلك عندما ينقر المستخدمون على عناصر التحكم في مستوى الصوت لمجموعة من المرات على التوالي ، يمتنع الهاتف عن التكبير أو التصغير.)
العودة إلى block
: هذا هو السبب في أن views/index.pug
تحدد block
الخاصة بنفس الأسماء الموجودة في views/layout.pug
. في حالة header_injection
هذه ، يتيح لنا ذلك استخدام CSS الخاص بالاتجاهات الرأسية أو الأفقية التي سيكون الهاتف بها.
content
هو المكان الذي نضع فيه الجزء المرئي الرئيسي من صفحة الويب ، والذي في هذه الحالة:
- حلقات من خلال مجموعة
group_names
، - يُنشئ عنصر
<span>
لكل عنصر باستخدامgroup_bar
لفئة CSS المطبق عليه ، و - ينشئ ارتباطًا داخل كل
<span>
بناءً على اسمgroup_name
.
فئة CSS group_bar
التي يمكننا تحديدها في الملف الذي تم سحبه عبر views/layout.pug
، أي public/stylesheets/style.css
:
html, body, form { padding: 0; margin: 0; height: 100%; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } .group_bar, .group_bar a, .remote_button { box-sizing: border-box; border: 1px solid white; color: greenyellow; background-color: black; } .group_bar { width: 100%; font-size: 6vh; text-align: center; display: inline-block; } .group_bar a { text-decoration: none; display: block; }
في هذه المرحلة ، إذا كان npm start
لا يزال قيد التشغيل ، فإن الانتقال إلى http://localhost:3000/
في متصفح سطح المكتب يجب أن يعرض زرين كبيرين جدًا لـ Netflix و YouTube ، مع توفر الباقي عن طريق التمرير لأسفل.
ولكن إذا نقرنا عليها في هذه المرحلة ، فلن تعمل ، لأننا لم نحدد بعد المسار الذي يرتبطون به ( GET
ting of /group
.)
إظهار تخطيط جهاز التحكم عن بعد المختار
للقيام بذلك ، سنضيف هذا إلى routes/group.js
قبل سطر module.exports
مباشرةً:
router.get('/', function(req, res, next) { const group_name = req.query.group_name || ''; const group = preset_commands[group_name]; return res.render('group', { keystroke_names: Object.keys(group.commands), group_name, title: `${group_name.match(/([AZ])/g).join('')}-Remote` }); });
سيؤدي هذا إلى إرسال اسم المجموعة إلى نقطة النهاية (على سبيل المثال ، عن طريق وضع ?group_name=Netflix
في نهاية /group/
) ، واستخدام ذلك للحصول على قيمة commands
من المجموعة المقابلة. هذه القيمة ( group.commands
) هي كائن ، ومفاتيح هذا الكائن هي الأسماء ( keystroke_names
) التي سنعرضها في تخطيط جهاز التحكم عن بعد الخاص بنا.
ملاحظة: لن يحتاج المطورون عديمي الخبرة إلى الدخول في تفاصيل كيفية عمله ، لكن قيمة title
تستخدم القليل من التعبيرات العادية لتحويل أسماء المجموعة / التخطيط إلى اختصارات - على سبيل المثال ، سيكون جهاز التحكم عن بُعد في YouTube الخاص بنا عنوان المتصفح YT-Remote
. بهذه الطريقة ، إذا كنا نصحح الأخطاء على جهازنا المضيف قبل تجربة الأشياء على الهاتف ، فلن يكون لدينا xdotool
نافذة متصفح جهاز التحكم عن بعد نفسه ، بدلاً من النافذة التي نحاول التحكم فيها. وفي الوقت نفسه ، سيكون العنوان لطيفًا وقصيرًا على هواتفنا ، إذا أردنا وضع إشارة مرجعية على جهاز التحكم عن بُعد.
كما هو الحال مع مواجهتنا السابقة مع res.render
، يرسل هذا الملف بياناته للاختلاط مع عروض النموذج views/group.pug
. سننشئ هذا الملف ونملأه بهذا:
extends layout block header_injection script(type='text/javascript', src='/javascript/group-client.js') block content form(action="/group?group_name=" + group_name, method="post") each keystroke_name in keystroke_names input(type="submit", name="keystroke_name", value=keystroke_name, class="remote_button")
كما هو الحال مع views/index.pug
، فإننا نتجاوز المدونتين من views/layout.pug
. هذه المرة ، لم نضع CSS في العنوان ، ولكن بعض JavaScript من جانب العميل ، والذي سنصل إليه قريبًا. (ونعم ، في لحظة من التوتر ، أعدت تسمية نصوص javascripts
التي تم جمعها بشكل غير صحيح ...)
content
الرئيسي هنا هو نموذج HTML مكون من مجموعة من أزرار الإرسال المختلفة ، واحد لكل اسم keystroke_name
. يرسل كل زر النموذج (إجراء طلب POST
) باستخدام اسم ضغط المفاتيح الذي يعرضه كقيمة يرسلها مع النموذج.
سنحتاج أيضًا إلى المزيد من CSS في ملف ورقة الأنماط الرئيسي لدينا:
.remote_button { float: left; width: calc(100%/3); height: calc(100%/3); font-size: 12vh; }
في وقت سابق ، عندما قمنا بإعداد نقطة النهاية ، انتهينا من معالجة الطلب مع:
return res.redirect(req.originalUrl);
هذا يعني بشكل فعال أنه عندما يرسل المستعرض النموذج ، يستجيب الطرف الخلفي لـ Node.js بإخبار المستعرض بالرجوع إلى الصفحة التي تم إرسال النموذج منها - أي التخطيط الرئيسي للتحكم عن بعد. سيكون أكثر أناقة بدون تبديل الصفحات ؛ ومع ذلك ، نريد أقصى قدر من التوافق مع العالم الغريب والرائع لمتصفحات الهاتف المحمول المتهالكة. بهذه الطريقة ، حتى بدون عمل JavaScript للواجهة الأمامية على الإطلاق ، يجب أن يظل مشروع Node.js الخلفي يعمل.
اندفاعة من الواجهة الأمامية لجافا سكريبت
الجانب السلبي لاستخدام نموذج لإرسال ضغطات المفاتيح هو أن المتصفح يجب أن ينتظر ، ثم ينفذ رحلة ذهابًا وإيابًا إضافية: يجب بعد ذلك طلب الصفحة وتوابعها من النهاية الخلفية Node.js الخاصة بنا وتسليمها. بعد ذلك ، يجب أن يتم عرضها مرة أخرى بواسطة المتصفح.
قد يتساءل القراء عن مدى تأثير ذلك. بعد كل شيء ، الصفحة صغيرة ، وتبعياتها ضئيلة للغاية ، وسيتم تشغيل مشروع Node.js النهائي عبر اتصال wifi محلي. يجب أن يكون إعدادًا بزمن انتقال منخفض ، أليس كذلك؟
كما اتضح - على الأقل عند الاختبار على الهواتف الذكية الأقدم التي تعمل بنظام Windows Phone 8.1 و Android 4.4.2 - فإن التأثير يكون ملحوظًا للأسف في الحالة الشائعة للنقر السريع لرفع أو خفض مستوى صوت التشغيل ببضع درجات. هنا حيث يمكن أن تساعد JavaScript ، دون الاستغناء عن احتياطينا الرشيق لـ POST
s اليدوية عبر نماذج HTML.
في هذه المرحلة ، يجب أن يكون عميلنا النهائي JavaScript (ليتم وضعه في public/javascript/group-client.js
) متوافقًا مع متصفحات الأجهزة المحمولة القديمة التي لم تعد مدعومة. لكننا لسنا بحاجة إلى الكثير منه:
(function () { function form_submit(event) { var request = new XMLHttpRequest(); request.open('POST', window.location.pathname + window.location.search, true); request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.send('keystroke_name=' + encodeURIComponent(event.target.value)); event.preventDefault(); } window.addEventListener("DOMContentLoaded", function() { var inputs = document.querySelectorAll("input"); for (var i = 0; i < inputs.length; i++) { inputs[i].addEventListener("click", form_submit); } }); })();
هنا ، ترسل وظيفة form_submit
البيانات عبر مكالمة غير متزامنة ، ويمنع السطر الأخير سلوك الإرسال العادي للمتصفحات ، حيث يتم تحميل صفحة جديدة بناءً على استجابة الخادم. النصف الأخير من هذا المقتطف ينتظر ببساطة حتى يتم تحميل الصفحة ثم يربط كل زر إرسال لاستخدام form_submit
. يتم تغليف كل شيء في IIFE.
اللمسات الأخيرة
هناك عدد من التغييرات على المقتطفات المذكورة أعلاه في الإصدار النهائي من كود البرنامج التعليمي للجهة الخلفية Node.js ، ومعظمها لأغراض معالجة الأخطاء بشكل أفضل:
- تتحقق النهاية الخلفية لـ Node.js الآن من أسماء المجموعات وضغطات المفاتيح المرسلة إليها للتأكد من وجودها. هذا الرمز في وظيفة يُعاد استخدامها لكل من وظائف
GET
وPOST
routes/group.js
. - نحن نستخدم نموذج
error
Pug إذا لم يفعلوا ذلك. - يعمل كل من JavaScript و CSS للواجهة الأمامية الآن على إنشاء مخطط مؤقت للأزرار باللون الرمادي أثناء انتظار استجابة من الخادم ، والأخضر بمجرد انتقال الإشارة طوال الطريق من خلال
xdotool
والعودة دون مشاكل ، والأحمر إذا لم يعمل أي شيء كما هو متوقع . - ستطبع النهاية الخلفية لـ Node.js تتبع مكدس إذا مات ، والذي سيكون أقل احتمالًا بالنظر إلى ما سبق.
القراء مدعوون للاطلاع على (و / أو استنساخ) مشروع Node.js الكامل على GitHub.
البرنامج التعليمي للجهة الخلفية Node.js ، الخطوة 5: اختبار في العالم الحقيقي
حان الوقت لتجربته على هاتف فعلي متصل بنفس شبكة wifi مثل المضيف الذي يقوم بتشغيل npm start
وفيلم أو مشغل موسيقى. إنها مجرد مسألة توجيه متصفح الويب الخاص بالهاتف الذكي إلى عنوان IP المحلي الخاص بالمضيف (مع :3000
ملحق به) ، والذي ربما يكون أسهل طريقة للعثور عليه عن طريق تشغيل hostname -I | awk '{print $1}'
hostname -I | awk '{print $1}'
في محطة طرفية على المضيف.
إحدى المشكلات التي قد يلاحظها مستخدمو Windows Phone 8.1 هي أن محاولة الانتقال إلى شيء مثل 192.168.2.5:3000
ستؤدي إلى ظهور خطأ منبثق:
لحسن الحظ ، لا داعي للإحباط: ما عليك سوى البدء بـ http://
أو إضافة لاحقة /
الحصول عليها لجلب العنوان دون مزيد من الشكوى.
يجب أن يقودنا اختيار خيار هناك إلى جهاز تحكم عن بعد يعمل.
لمزيد من الراحة ، قد يرغب المستخدمون في ضبط إعدادات DHCP لجهاز التوجيه الخاص بهم لتعيين عنوان IP نفسه دائمًا للمضيف ، ووضع إشارة مرجعية على شاشة تحديد التخطيط و / أو أي تخطيطات مفضلة.
نرحب بطلبات السحب
من المحتمل ألا يحب الجميع هذا المشروع تمامًا كما هو. فيما يلي بعض الأفكار للتحسينات ، لأولئك الذين يرغبون في التعمق أكثر في الكود:
- يجب أن يكون من السهل تعديل التخطيطات أو إضافة تخطيطات جديدة لخدمات أخرى ، مثل Disney Plus.
- ربما يفضل البعض تنسيق "الوضع الخفيف" وخيار التبديل بينهما.
- التراجع عن Netflix ، نظرًا لأنه غير قابل للعكس ، يمكن حقًا استخدام "هل أنت متأكد؟" تأكيدا من نوع ما.
- سيستفيد المشروع بالتأكيد من دعم Windows.
- تشير وثائق
xdotool
إلى OSX ، فهل هذا (أو يمكن لهذا) المشروع أن يعمل على جهاز Mac حديث؟ - للاستراحة المتقدمة ، طريقة للبحث عن الأفلام واستعراضها ، بدلاً من الاضطرار إلى اختيار فيلم Netflix / Amazon Prime Video واحد أو إنشاء قائمة تشغيل على YouTube على الكمبيوتر.
- مجموعة اختبار آلية ، في حالة تعطل أي من التغييرات المقترحة الوظيفة الأصلية.
آمل أن تكون قد استمتعت بهذا البرنامج التعليمي الخلفي لـ Node.js وتجربة وسائط محسنة نتيجة لذلك. استمتع بالبث المباشر والبرمجة!