การเข้ารหัส Cabin Fever: บทช่วยสอน Back-end ของ Node.js

เผยแพร่แล้ว: 2022-03-11

การล็อกดาวน์จากโควิด-19 ทำให้พวกเราหลายคนต้องกักตัวอยู่แต่ในบ้าน หวังว่าแค่ไข้ในห้องโดยสารจะเป็นไข้ที่แย่ที่สุดที่เราจะประสบ พวกเราหลายคนบริโภคเนื้อหาวิดีโอมากกว่าที่เคย แม้ว่าการออกกำลังกายจะมีความสำคัญเป็นพิเศษในตอนนี้ แต่ในบางครั้ง ก็มีความคิดถึงถึงความหรูหราของรีโมทคอนโทรลที่ล้าสมัยเมื่อแล็ปท็อปอยู่ไกลเกินเอื้อม

นั่นคือที่มาของโปรเจ็กต์นี้: โอกาสในการเปลี่ยนสมาร์ทโฟนเครื่องใดก็ได้ แม้กระทั่งสมาร์ทโฟนรุ่นเก่าที่ไม่มีประโยชน์หากไม่มีการอัปเดต ให้กลายเป็นรีโมตที่ใช้งานสะดวกสำหรับ Netflix/YouTube/Amazon Prime Video/ฯลฯ ถัดไป การดื่มสุราดู นอกจากนี้ยังเป็นบทช่วยสอนส่วนหลังของ Node.js: โอกาสในการเรียนรู้พื้นฐานของแบ็กเอนด์ JavaScript โดยใช้เฟรมเวิร์ก Express และเอ็นจิ้นเทมเพลต Pug (เดิมคือ Jade)

หากฟังดูน่ากลัว โปรเจ็กต์ Node.js ที่สมบูรณ์จะถูกนำเสนอในตอนท้าย ผู้อ่านต้องการเพียงแค่เรียนรู้มากเท่าที่พวกเขาสนใจในการเรียนรู้ และจะมีคำอธิบายที่อ่อนโยนกว่าเกี่ยวกับพื้นฐานบางประการจำนวนพอสมควรเพื่อให้ผู้อ่านที่มีประสบการณ์มากขึ้นสามารถข้ามไปได้

ทำไมไม่เพียงแค่...?

ผู้อ่านอาจสงสัยว่า "ทำไมต้องเขียนโค้ด Node.js แบ็กเอนด์" (นอกจากโอกาสในการเรียนรู้แน่นอน) “มันไม่มีแอพอยู่แล้วเหรอ?”

แน่นอน—มีมากมาย แต่มีเหตุผลหลักสองประการที่อาจไม่เป็นที่ต้องการ:

  1. สำหรับผู้ที่พยายามปรับเปลี่ยนโทรศัพท์รุ่นเก่า นี่อาจ ไม่ใช่ตัวเลือกอีกต่อไป เช่นเดียวกับอุปกรณ์ Windows Phone 8.1 ที่ฉันต้องการใช้ (ร้านแอปปิดตัวลงอย่างเป็นทางการในปลายปี 2019)
  2. ความไว้วางใจ (หรือขาดมัน) เช่นเดียวกับแอปจำนวนมากที่พบในแพลตฟอร์มมือถือใด ๆ พวกเขามักจะมาพร้อมกับความต้องการของผู้ใช้ที่ให้สิทธิ์มากกว่าที่แอปต้องการสำหรับสิ่งที่ตั้งใจจะทำ แต่แม้ว่าแง่มุมนี้จะถูกจำกัดอย่างเหมาะสม แต่ลักษณะของแอปควบคุมระยะไกลหมายความว่าผู้ใช้ยังคงต้องเชื่อมั่นว่านักพัฒนาแอปจะไม่ใช้สิทธิ์ของตนในทางที่ผิดบนเดสก์ท็อปของโซลูชันโดยรวมสปายแวร์หรือมัลแวร์อื่นๆ

ปัญหาเหล่านี้มีมาช้านานแล้วและยังเป็นแรงจูงใจสำหรับโครงการที่คล้ายกันในปี 2014 ที่พบใน GitHub nvm ทำให้ง่ายต่อการติดตั้ง Node.js เวอร์ชันเก่า และแม้ว่าการขึ้นต่อกันบางอย่างจำเป็นต้องอัปเกรด แต่ Node.js ก็มีชื่อเสียงในด้านความเข้ากันได้แบบย้อนหลัง

น่าเสียดายที่ bitrot ชนะ แนวทางที่ดื้อรั้นและความเข้ากันได้ของ Node.js แบ็คเอนด์ไม่ตรงกับการเลิกใช้งานที่ไม่มีที่สิ้นสุดและการวนซ้ำการพึ่งพาที่เป็นไปไม่ได้ในเวอร์ชันเก่าของ Grunt, Bower และส่วนประกอบอื่น ๆ อีกนับสิบ หลายชั่วโมงต่อมา เป็นที่ชัดเจนว่าการเริ่มต้นใหม่จากศูนย์จะง่ายกว่ามาก—คำแนะนำของผู้เขียนเองที่ต่อต้านการคิดค้นล้อใหม่แม้ว่า

Gizmos ใหม่จากรุ่นเก่า: เปลี่ยนโทรศัพท์เป็นรีโมทคอนโทรลโดยใช้ Node.js Back End

ก่อนอื่น โปรดทราบว่าโปรเจ็กต์ 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 มากกว่า ดังนั้นหากคุณได้ติดตั้งไว้ ให้รัน yarn โดยไม่มีพารามิเตอร์

ในกรณีนี้ ควรปลอดภัยที่จะเพิกเฉยต่อคำเตือนการเลิกใช้งาน (หวังว่าจะได้รับการแก้ไขเร็วๆ นี้) จากการอ้างอิงย่อยของ Pug ตราบใดที่การเข้าถึงยังคงอยู่ตามความจำเป็นบนเครือข่ายท้องถิ่น

การ yarn start อย่างรวดเร็วหรือ npm start ตามด้วยการนำทางไปยัง localhost:3000 ในเบราว์เซอร์ แสดงให้เห็นว่าแบ็กเอนด์ Node.js พื้นฐานแบบ Express ของเราใช้งานได้ เราสามารถฆ่ามันด้วย Ctrl+C

บทช่วยสอน Back-end ของ Node.js ขั้นตอนที่ 2: วิธีส่งการกดแป้นบนเครื่องโฮสต์

เมื่อส่วน ระยะไกล เสร็จไปแล้วครึ่งทาง เรามาสนใจส่วน ควบคุม กัน เราต้องการบางสิ่งที่สามารถควบคุมเครื่องโดยทางโปรแกรมได้ เราจะเรียกใช้แบ็คเอนด์ Node.js ของเรา โดยแกล้งทำเป็นว่ากำลังกดปุ่มบนแป้นพิมพ์

สำหรับสิ่งนั้น เราจะติดตั้ง xdotool โดยใช้คำแนะนำอย่างเป็นทางการ การทดสอบตัวอย่างคำสั่งอย่างรวดเร็วในเทอร์มินัล:

 xdotool search "Mozilla Firefox" windowactivate --sync key --clearmodifiers ctrl+l

…ควรทำตามที่กล่าวไว้โดยสมบูรณ์ สมมติว่า Mozilla Firefox เปิดอยู่ในขณะนั้น ดีแล้ว! ง่ายที่จะให้โปรเจ็กต์ Node.js ของเราเรียกใช้เครื่องมือบรรทัดคำสั่งเช่น xdotool อย่างที่เราจะได้เห็นในเร็วๆ นี้

บทช่วยสอน Back-end ของ Node.js ขั้นตอนที่ 3: การออกแบบฟีเจอร์

สิ่งนี้อาจไม่เป็นจริงสำหรับทุกคน แต่โดยส่วนตัวแล้ว ฉันพบว่ารีโมตคอนโทรลที่ทันสมัยจำนวนมากมีปุ่มมากกว่าที่ฉันเคยใช้ประมาณห้าเท่า สำหรับโปรเจ็กต์นี้ เรากำลังดูเลย์เอาต์แบบเต็มหน้าจอที่มีปุ่มขนาดใหญ่ 3 ต่อ 3 ที่กำหนดเป้าหมายได้ง่าย ขึ้นอยู่กับความชอบส่วนตัวว่าปุ่มทั้งเก้านั้นเป็นอย่างไร

ปรากฎว่าแป้นพิมพ์ลัดสำหรับฟังก์ชันที่ง่ายที่สุดนั้นไม่เหมือนกันใน Netflix, YouTube และ Amazon Prime Video บริการเหล่านี้ไม่สามารถใช้งานได้กับคีย์สื่อทั่วไปเช่นแอปเครื่องเล่นเพลงแบบเนทีฟ นอกจากนี้ ฟังก์ชันบางอย่างอาจไม่สามารถใช้ได้กับบริการทั้งหมด

ดังนั้นสิ่งที่เราต้องทำคือกำหนดเลย์เอาต์การควบคุมระยะไกลที่แตกต่างกันสำหรับแต่ละบริการและจัดเตรียมวิธีการสลับไปมาระหว่างบริการเหล่านั้น

การกำหนดเลย์เอาต์การควบคุมระยะไกลและการจับคู่กับแป้นพิมพ์ลัด

มาดูการสร้างต้นแบบอย่างรวดเร็วด้วยการตั้งค่าล่วงหน้าจำนวนหนึ่ง เราจะใส่ไว้ใน common/preset_commands.js —“common” เพราะเราจะรวมข้อมูลนี้จากไฟล์มากกว่าหนึ่งไฟล์:

 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 จะทำงาน—เพียงเพื่อให้ความตั้งใจของเราชัดเจน

บทช่วยสอน Back-end ของ Node.js ขั้นตอนที่ 4: จุดสิ้นสุด “คีย์” ของ API ของเรา (Pardon the Pun)

เราต้องการจุดปลายเพื่อ POST ซึ่งจะจำลองการกดแป้นพิมพ์โดยใช้ 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 ผ่านการเรียกเชลล์ของระบบใน 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;

ที่นี่ เราคว้าคีย์ "ชื่อ" ที่ร้องขอจากเนื้อหาของคำขอ POST ( req.body ) ภายใต้พารามิเตอร์ที่ชื่อ keystroke_name มันก็จะประมาณนี้ . จากนั้นเราใช้สิ่งนั้นเพื่อค้นหาโค้ดที่เกี่ยวข้องจากออบเจ็กต์ commands ของ preset_commands['YouTube']

คำสั่งสุดท้ายอยู่บนมากกว่าหนึ่งบรรทัด ดังนั้น \ s ที่ส่วนท้ายของแต่ละบรรทัดจะรวมชิ้นส่วนทั้งหมดเป็นคำสั่งเดียว:

  • search "YouTube" เรียกหน้าต่างแรกที่มีคำว่า “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 and Executed ในหน้าต่าง npm และที่สำคัญกว่านั้น ให้เปิดเบราว์เซอร์ขึ้นมาและหยุดวิดีโอชั่วคราว การดำเนินการคำสั่งนั้นอีกครั้งควรให้ผลลัพธ์เดียวกันและยกเลิกการหยุดชั่วคราว

บทช่วยสอน Back-end ของ Node.js ขั้นตอนที่ 5: เลย์เอาต์การควบคุมระยะไกลหลายรายการ

แบ็คเอนด์ของเรายังไม่เสร็จ เรายังต้องการเพื่อให้สามารถ:

  1. สร้างรายการเลย์เอาต์การควบคุมระยะไกลจาก preset_commands
  2. สร้างรายชื่อ "ชื่อ" ของการกดแป้นพิมพ์เมื่อเราเลือกเลย์เอาต์การควบคุมระยะไกลโดยเฉพาะ (เรายังสามารถเลือกใช้ common/preset_commands.js ได้โดยตรงที่ส่วนหน้า เนื่องจากเป็น JavaScript แล้ว และกรองไว้ที่นั่น นั่นเป็นหนึ่งในข้อดีที่เป็นไปได้ของส่วนหลังของ Node.js เราแค่ไม่ได้ใช้ที่นี่ .)

คุณลักษณะทั้งสองนี้เป็นที่ที่บทช่วยสอนส่วนหลังของ Node.js ตัดกับส่วนหน้าที่ใช้ Pug ที่เราจะสร้าง

การใช้ Pug Templating เพื่อแสดงรายการรีโมทคอนโทรล

ส่วนหลังของสมการหมายถึงการแก้ไข 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 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 เป็นที่ที่เราวางส่วนหลักที่มองเห็นได้ของหน้าเว็บ ซึ่งในกรณีนี้:

  1. วนซ้ำผ่านอาร์เรย์ group_names ที่เราส่งผ่าน
  2. สร้างองค์ประกอบ <span> สำหรับแต่ละองค์ประกอบโดยใช้คลาส CSS group_bar และ
  3. สร้างลิงค์ภายในแต่ละ <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 โดยที่เหลือจะมีให้โดยเลื่อนลงมา

การทดสอบตัวเลือกเลย์เอาต์ของรีโมตคอนโทรลโดยใช้เบราว์เซอร์เดสก์ท็อป โดยแสดงปุ่มขนาดใหญ่มากสองปุ่มสำหรับ Netflix และ YouTube

แต่ถ้าเราคลิกไปที่จุดนี้ มันจะไม่ทำงาน เพราะเรายังไม่ได้กำหนดเส้นทางที่พวกเขาลิงก์ไป (การ GET ting ของ /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 จะตอบสนองโดยบอกให้เบราว์เซอร์กลับไปที่หน้าที่ส่งแบบฟอร์มมา เช่น เลย์เอาต์หลักของรีโมตคอนโทรล มันจะดูสง่างามมากขึ้นโดยไม่ต้องเปลี่ยนหน้า อย่างไรก็ตาม เราต้องการความเข้ากันได้สูงสุดกับโลกที่แปลกประหลาดและมหัศจรรย์ของเบราว์เซอร์มือถือที่เสื่อมสภาพ ด้วยวิธีนี้ แม้ว่า front-end JavaScript จะไม่ทำงานเลย โปรเจ็กต์แบ็คเอนด์ Node.js ของเราก็ยัง ควร ทำงาน

Dash of Front-end JavaScript

ข้อเสียของการใช้แบบฟอร์มเพื่อส่งการกดแป้นคือเบราว์เซอร์ต้องรอ จากนั้นจึงดำเนินการไปกลับเพิ่มเติม: หน้าและการอ้างอิงจะต้องได้รับการร้องขอจากส่วนหลังของ Node.js และนำส่ง จากนั้นเบราว์เซอร์จะต้องแสดงผลอีกครั้ง

ผู้อ่านอาจสงสัยว่าสิ่งนี้จะมีผลกระทบมากน้อยเพียงใด ท้ายที่สุด หน้าเพจมีขนาดเล็ก การขึ้นต่อกันของหน้านั้นน้อยมาก และโปรเจ็กต์ Node.js สุดท้ายของเราจะทำงานผ่านการเชื่อมต่อ wifi ในพื้นที่ ควรเป็นการตั้งค่าเวลาแฝงต่ำใช่ไหม

ตามที่ปรากฎ—อย่างน้อยเมื่อทำการทดสอบบนสมาร์ทโฟนรุ่นเก่าที่ใช้ Windows Phone 8.1 และ Android 4.4.2— ผลกระทบที่เห็นได้ชัดคือในกรณีทั่วไปของการแตะอย่างรวดเร็วเพื่อเพิ่มหรือลดระดับเสียงของการเล่นลงเล็กน้อย JavaScript สามารถช่วยได้ ที่นี่ โดยไม่ต้องละทิ้งทางเลือกที่สวยงามของ POST ด้วยตนเองผ่านแบบฟอร์ม 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

บทช่วยสอน Back-end ของ 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 จะแสดงป๊อปอัปข้อผิดพลาด:

ภาพหน้าจอของข้อความแสดงข้อผิดพลาดของ Windows Phone ชื่อ "ที่อยู่ที่ไม่รองรับ" โดยระบุว่า "Internet Explorer Mobile ไม่สนับสนุนที่อยู่ประเภทนี้และไม่สามารถแสดงหน้านี้ได้

โชคดีที่คุณไม่จำเป็นต้องท้อใจ: เพียงนำหน้าด้วย http:// หรือเพิ่มส่วนท้าย / เรียกข้อมูลที่อยู่โดยไม่ต้องร้องเรียนเพิ่มเติม

หน้าจอการเลือกเลย์เอาต์ของรีโมทคอนโทรล

การเลือกตัวเลือกควรนำเราไปสู่รีโมทคอนโทรลที่ใช้งานได้

หน้าจอรีโมทคอนโทรล "Generic/Music Player"

เพื่อความสะดวกยิ่งขึ้น ผู้ใช้อาจต้องการปรับการตั้งค่า DHCP ของเราเตอร์เพื่อกำหนดที่อยู่ IP เดียวกันให้กับโฮสต์เสมอ และบุ๊กมาร์กหน้าจอการเลือกเลย์เอาต์และ/หรือเลย์เอาต์โปรดใดๆ

ดึงคำขอยินดีต้อนรับ

เป็นไปได้ว่าไม่ใช่ทุกคนที่จะชอบโครงการนี้อย่างที่มันเป็น ต่อไปนี้คือแนวคิดบางประการสำหรับการปรับปรุง สำหรับผู้ที่ต้องการเจาะลึกลงไปในโค้ด:

  • คุณควรปรับแต่งเลย์เอาต์หรือเพิ่มเลย์เอาต์ใหม่สำหรับบริการอื่นๆ เช่น Disney Plus อย่างตรงไปตรงมา
  • บางทีบางคนอาจชอบเลย์เอาต์ "โหมดแสง" และตัวเลือกเพื่อสลับไปมาระหว่างกัน
  • การเลิกใช้ Netflix เนื่องจากไม่สามารถย้อนกลับได้จึงสามารถใช้ "คุณแน่ใจ" ได้จริงๆ หรือ การยืนยันบางอย่าง
  • โครงการนี้จะได้รับประโยชน์จากการสนับสนุน Windows อย่างแน่นอน
  • เอกสารประกอบของ xdotool กล่าวถึง OSX— โปรเจ็กต์นี้ (หรืออาจเป็นไปได้) ทำงานบน Mac รุ่นใหม่หรือไม่
  • สำหรับการพักผ่อนขั้นสูง วิธีในการค้นหาและเรียกดูภาพยนตร์ แทนที่จะต้องเลือกภาพยนตร์ Netflix/Amazon Prime Video เรื่องเดียวหรือสร้างเพลย์ลิสต์ YouTube บนคอมพิวเตอร์
  • ชุดทดสอบอัตโนมัติ ในกรณีที่การเปลี่ยนแปลงใด ๆ ที่แนะนำทำให้ฟังก์ชันการทำงานเดิมเสียหาย

ฉันหวังว่าคุณจะสนุกกับการกวดวิชาแบ็คเอนด์ Node.js และประสบการณ์ด้านสื่อที่ได้รับการปรับปรุงให้ดีขึ้น มีความสุขในการสตรีม—และการเข้ารหัส!

ที่เกี่ยวข้อง: การสร้าง Node.js/TypeScript REST API ส่วนที่ 1: Express.js