ToCode
Ir al canal en Telegram
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
+130 días
Archivo de publicaciones
1 419
# מה אפשר להגיד במקום ״אי אפשר״
אי אפשר הורס את המסיבה. מ"אי אפשר" אין לאן להתקדם והוא כמעט אף פעם לא נכון.
ברור שיש דברים שאי אפשר לעשות, אבל אם הבוס או הלקוח ביקש משהו זה כנראה בגלל שהוא רוצה משהו שהוא ראה במקום אחר, או משהו שנראה לו הגיוני. אותו בוס או לקוח לא רואה את כל מערכת האילוצים שלכם, ולכן גם לא מבין אותה.
כשאני מחפש ברשת ומגלה שלאקסיוס אין תמיכה בהגבלת מספר ה Redirects בעבודה מהדפדפן, אני יכול להפוך את זה למסר ללקוח ולהגיד שבמערכת אי אפשר יהיה לוותר על מעקב אחרי Redirects.
או כשאני מחפש ברשת ומגלה שבפרויקט create-react-app ה jest לא תומך בהוספת בייבל פלאגין, אני יכול לתרגם את זה למסר ללקוח ולהגיד שאי אפשר לבדוק את הפרויקט אם נשתמש בספריית npm מסוימת שצריכה המרה עם פלאגין שבמקרה לא מופיע בברירת המחדל.
אבל שני המקרים יעבדו טוב יותר אם נתקשר ללקוח את סט האילוצים והעלויות ונמצא פיתרון שכן אפשר לעשות-
1. יכול להיות שהגבלת ה Redirects באקסיוס מאוד חשובה, מספיק חשובה בשביל לתרום לאקסיוס קוד שתומך בזה.
2. יכול להיות שהבחירה באקסיוס לא כזאת חשובה, ואפשר לערגן מחדש את הקוד עם Fetch API או ספריה אחרת.
3. יכול להיות שאפשר לעדכן את קוד צד השרת או להוסיף פרוקסי, כדי לבטל את הצורך בהגבלת מספר ה Redirects.
או במקרה של ג'סט, יכול להיות שאפשר להשתמש בספריית בדיקה אחרת; יכול להיות שאפשר להפעיל eject או לוותר על create-react-app לחלוטין.
"בוא נדבר על זה" הוא תמיד אופציה טובה יותר מ"אי אפשר". וגם אם בסוף מחליטים לוותר על הפיצ'ר או התיקון, כולם יוצאים עם הרגשה הרבה יותר טובה.
1 419
# לא מכיר את הכללים
הרבה יותר קל ללמוד את הפירוש של מילה חדשה בשפה העברית מאשר מילה חדשה בשפה זרה שאף פעם לא שמעתם. המילה או הביטוי החדש בעברית כמעט אף פעם לא יהיו חדשים לגמרי. בתור דוברי שפת אם של השפה יש סיכוי טוב שכבר נתקלתם במילה הזאת איפשהו, וגם אם לא אז בטוח נתקלתם בהשפעה שלה או במילים דומות לה.
וגם ההיפך נכון - הקשבה למילה בשפה חדשה יכולה להיות מאוד מתסכלת, כי אני אפילו לא יודע לאיזה צלילים צריך לשים לב, ולכן כשאני אנסה לחזור על המילה בקול רם תצא לי בכלל מילה אחרת. ואיך אפשר לזכור מילה שאתה אפילו לא מצליח להגיד אותה?
חוסר היכרות עם הכללים הוא מהגורמים המרכזיים לתסכול בתהליך הלמידה. אנחנו שומעים הסבר, בטוחים שאנחנו מבינים מה היה בו ואז באים ליישם את זה וכלום לא עובד. אני מנסה לשאול שאלה ואנשים אפילו לא מבינים מה אני שואל.
וככה בן אדם יכול לשאול בפורום "בשביל מה בכלל צריך פרמטרים לפונקציה אם אני יכול להשתמש במשתנים גלובאליים", ואתה אפילו לא יודע איך להתחיל לענות על זה. וכנראה גם חלק מהשאלות שלי נשמעות ככה בנושאים שאני חדש בהם.
דרך אחת להתגבר על התסכול היא להכיר בפער ולחקור אותו. להבין שייקח לי עוד זמן עד שאצליח בכלל לשאול את השאלות הנכונות, ובינתיים עדיף לכתוב קוד ולקרוא קוד של אחרים רק בשביל לחיות בתוך השפה ולגבש את המושגים. להסתכל מה אחרים שואלים בפורומים ומה עונים להם, ולתת לזה זמן. רק אחרי שמצליחים לשמוע את המילים אפשר להתחיל לחפש את המשמעות.
1 419
# איך לסנן מערך עם גודל תוצאה מקסימלי ב JavaScript
הפונקציה filter של JavaScript יודעת לסנן מערך לפי תנאי מסוים. אבל מה קורה אם אתם רוצים להגביל את מספר התוצאות ש filter תחזיר? הנה דוגמה קטנה והצעה לפיתרון-
## הקוד של popular-english-wods
בקוד של popular-english-words, בה השתמשתי לפני כמה ימים כדי לכתוב חיקוי לוורדל, מצאתי את הפונקציה הבאה:
getMostPopularFilter(count, test) {
var result = []
list.every(word => {
if (test(word)) {
result.push(word)
}
if (result.length == count) return false
return true
})
return result
}
מטרת הפונקציה לקחת מערך של מילים מהמשתנה list, לסנן ממנו רק מילים שמתאימות לתנאי מסוים שמתקבל בפרמטר test ולהחזיר לא יותר מ count מילים שמתאימות לתנאי.
הפונקציה עובדת אבל קריאה שלה משאירה טעם רע בפה. במבט ראשון זה מרגיש כאילו אפשר היה לסגור את זה בשורה אחת עם filter. אבל במבט מעמיק יותר אנחנו מבינים ש Array.prototype.filter לא באמת היה עוזר לנו כאן, בגלל מגבלת האורך. אם הייתי מנסה לשכתב את זה להשתמש בפילטר הקוד היה נראה כך:
return list.filter(test).slice(0, count);
הרבה יותר יפה מהגירסה המקורית, אבל גם הרבה פחות יעיל. בגלל שהחיתוך נעשה אחרי הסינון, אז ברשימת מילים גדולה הקוד יעשה הרבה עבודה מיותרת.
## אם רק היו לנו Generators ...
ג'נרטור הוא דרך לייצג סידרה של ערכים בלי לחשב אותם. תחשבו על דף הוראות של איקאה במקום ארון מורכב. אם יום אחד נרצה להרכיב את הארון נלך לפי ההוראות ב Generator, אבל הרבה יותר נוח לשים בקופסה את החלקים ודף ההוראות מאשר ארון מורכב מלא.
ב JavaScript אני יכול לכתוב Generator אם אוסיף כוכבית לפני שם הפונקציה. פונקציה רגילה מחזירה ערך, ו Generator מחזיר משהו שיודע לחשב ערכים. בתוך פונקציית ה Generator אני משתמש במילה yield כדי להחזיר את הערך הבא, ובשום שלב לא צריך לחשב מראש את כל הערכים של הסידרה.
בדוגמה של פילטר אני יכול לכתוב את ה Generator הבא:
function *ifilter(arr, p) {
for (let x of arr) {
if (p(x)) {
yield x;
}
}
}
הלולאה for ... of יודעת לרוץ גם על Generator וגם על מערכים רגילים. הפקודה yield אומרת שאנחנו יכולים לבקש "ערך אחד" מתוך ה Generator, ואז הפונקציה מחזירה את הערך וגם זוכרת את המקום שבו הפסיקה לרוץ. כשנבקש את הערך הבא הלולאה תמשיך מאותה נקודה אחרי ה yield האחרון שרץ.
אם אנסה להפעיל קוד כזה:
const res = ifilter([10, 20, 30], x => x > 10);
console.log(res);
התוצאה לא תהיה המערך של כל האיברים שגדולים מ 10, אלא:
Object [Generator] {}
בשביל להפוך את ה Generator למערך אני משתמש ב Array.prototype.from כלומר:
const res = ifilter([10, 20, 30], x => x > 10);
console.log(Array.from(res));
ורק בהפעלה של Array.from בעצם מתבצע הסינון.
## הרכבת Generators
מה שיפה ב Generators זה שאם יש לי יותר מאחד, אני יכול לחבר אותם ביחד ולהפוך את התוצאה למערך רק בסוף. אז נכתוב גם את islice:
function *islice(arr, start, count) {
for (let i=0; i < start; i++) {
arr.next();
}
for (let i=0; i < count; i++) {
const { value, done } = arr.next();
if (done) { return; }
yield value;
}
}
ופה כבר יש טריק חדש - הפונקציה next של Generator "מזיזה" את הגנרטור איבר אחד קדימה בלי להחזיר אותו. היא מחזירה אוביקט שמכיל את המפתח value עם הערך שהתקבל, והמפתח done שמקבל true אם אין יותר ערכים בגנרטור.
בשביל לשלב את שניהם ביחד אני יכול לכתוב:
return Array.from(
islice(
ifilter(list, test),
0,
count));
וזה כבר יותר דומה לגירסת השורה אחת שרציתי להגיע אליה בהתחלה, אבל עם הבונוס של הביצועים. הגנרטור islice יעצור אחרי count איברים ולא ימשוך יותר איברים מהגנרטור ifilter, ולכן ifilter יחשב רק את האיברים שהוא באמת צריך.1 419
# על שליחת פרמטרים בלי Content Type (ולמה התקלקל לי הדמו בוובינר אתמול)
במהלך וובינר על Next.JS שהעברתי אתמול ניסיתי לשלוח הודעה לשרת מתוך curl עם הפקודה הבאה:
$ curl -X POST -d hello 'localhost:3000/api/todos'
קוד צד השרת שקיבל את ההודעה נראה כך:
export default function handler(req, res) {
console.log(`add ${JSON.stringify(req.body)}`);
addTodo(req.query.text);
console.log(`todos = ${JSON.stringify(getTodos())}`);
res.status(200).json({});
}
וכמו שקורה לעתים קרובות כשמנסים לאלתר במהלך Live Coding הקוד לא עבד. במקום ש req.body יכיל את המילה hello כמו שהתכוונתי שיקרה, הוא הכיל אוביקט. תוצאת ההדפסה היתה:
add {"hello":""}
כלומר req.body הכיל אוביקט עם מפתח יחיד בשם hello וערך ריק.
מה?
בחזרה ל curl, המתג -d אומר שמה שיבוא אחריו הוא גוף ההודעה, והמתג -X POST אומר שאני שולח בקשה מסוג POST. בסך הכל יש לנו בקשת POST שגוף ההודעה שלה מורכב מהמילה הבודדת hello. אז איך req.body הגיע להיות אוביקט, ואיך hello הגיע להיות המפתח?
## החשיבות של Contet Type
התשובה נעוצה באיך ש next.js, וכמוהו Web Frameworks רבים בצד השרת, מפענח את גוף ההודעה שמקבל והופך אותו מטקסט פשוט לתוכן של req.body. בתיעוד של next מסבירים לנו ש req.body הוא:
> req.body - An object containing the body parsed by content-type, or null if no body was sent
וזה מסביר למה בתוך req.body לא קיבלתי בדיוק את הטקסט שאותו שלחתי בתוכן הבקשה. אותה מילה, hello, עוברת פיענוח והופכת לאוביקט לפי כללי פיענוח שנקבעים לפי הכותרת content-type. בהמשך אותו דף תיעוד מסבירים לנו גם שכל נתיב API יכול לייצא אוביקט בשם config בעזרתה אפשר לבטל את מנגנון פיענוח ה body האוטומטי. אבל ביטול מנגנון הפיענוח האוטומטי לא ממש יעזור לי כאן. בלי מנגנון זה אני אקבל את גוף ההודעה בתור Stream ואצטרך לקרוא קטעים ממנו ולחבר אותם יחד לטקסט אחד ארוך כשההודעה מסתיימת, וחבל לכתוב קוד למנגנון שכבר עובד בפריימוורק.
במקום לבטל את מנגנון הפיענוח אני יכול להגיד לו בצורה מדויקת יותר מה אני רוצה. העברה של המילה application/json בתור content-type תגרום למפענח להתיחס לגוף ההודעה בתור JSON Object. העברה של כל מילה שהוא לא מכיר כמו foobar או text תגרום למפענח להסתכל על גוף ההודעה בתור טקסט ולהחזיר בתוך req.body מחרוזת פשוטה עם התוכן - שזה בעצם מה שרציתי שיקרה. והעברה של המילה application/x-www-form-urlencoded, שהיא גם ברירת המחדל כלומר היא שיטת העבודה שתיבחר אם לא כתבתי שום ערך ל content-type, תגרום למפענח להפוך את גוף ההודעה לאוסף של פרמטרים בפורמט של key=value.
אז הנה הסיכום של שלוש פקודות curl שאני מריץ והתוצאה שאני מקבל ב req.body עבור כל אחת:
$ curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -d hello 'localhost:3000/api/todos'
# req.body = {"hello":""}
$ curl -X POST -H 'Content-Type: text/plain' -d hello 'localhost:3000/api/todos'
# req.body = "hello"
$ curl -X POST -H 'Content-Type: application/json' -d hello 'localhost:3000/api/todos'
# Error - Invalid JSON
חוץ מזה אפשר לתפוס הקלטה של הוובינר בערוץ יוטיוב של ניר (מומלץ לבקר שם בכל מקרה) בקישור https://www.youtube.com/watch?v=OjMqzBWuTl0 ובימים הקרובים היא גם תעלה פה לאתר לאזור הקלטות מוובינרים.1 419
הי חברים עוד חצי שעה זום יחד עם ניר פריזיאן על Next.JS, ופעם ראשונה יעבור גם ביוטיוב לייב. מוזמנים להצטרף לזום או לצפות דרך היוטיוב כאן:
https://www.youtube.com/watch?v=OjMqzBWuTl0
1 419
</div> ); }צריך להגיד - זה לא המנגנון שג'וש וורדל כתב. במשחק של וורדל הוא יצר מקלדת וירטואלית משלו כדי שיוכל לסמן עליה איזה אותיות כבר ניסיתם. אבל הוא גם עשה אנימציות, סינן טוב יותר את רשימת המילים ומכר את המשחק בסכום בן 7 ספרות. פה אנחנו בכל זאת רק כותבים פוסט. ## קומפוננטה ראשית App בשביל לחבר את הכל יחד אני משתמש בקומפוננטה ראשית בשם App שמציגה את ה WordInput למעלה ואחריו את כל הניחושים שהיו:
export default function App() {
const [selectedWord, setSelectedWord] = useState(selectRandomWord());
const [guesses, setGuesses] = useState([]);
function submit(word) {
setGuesses([word, ...guesses]);
}
return (
<div className="App">
<WordInput key={guesses.length} onSubmit={submit} />
{guesses.map((w, i) => (
<ReadOnlyWordInput word={w} correctWord={selectedWord} key={i} />
))}
</div>
);
}
## רגע רגע, מאיפה המילים?
שמתם לב ש App קוראת לפונקציה selectRandomWord כדי לקבל מילה אקראית. בואו נראה איך זה עובד:
import _ from "lodash";
import { words } from "popular-english-words";
function selectRandomWord() {
return _.sample(words.getMostPopularLength(2000, 5));
}
הספריה popular-english-words ב npm יודעת להחזיר רשימות מילים באנגלית בכל אורך של רשימה שנרצה ובכל אורך של מילה שנרצה. אין שם את המילים הכיפיות של ג'וש, אבל בשביל חיקוי זה מספיק טוב.
אתם מוזמנים לחטט בקוד המלא כולל ה CSS בקודסנדבוקס בקישור https://codesandbox.io/s/white-tree-67euq
או למי מכם שקורא מהאתר גם בגירסה המוטמעת:
<iframe src="https://codesandbox.io/embed/white-tree-67euq?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="white-tree-67euq"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>1 419
# בואו נכתוב וורדל רק בשביל לאכול את הלב
ג׳וש וורדל מכר את המשחק הממכר שהוא כתב לניו יורק טיימס בסכום בן 7 ספרות ואנחנו יכולים לחגוג איתו באמצעות כתיבת קלון מהיר למשחק בריאקט ובדרך גם ללמוד כמה דברים על מילים וממשקים.
## חוקי המשחק
אם בטעות עוד לא יצא לכם לשחק, אז וורדל הוא משחק מילים בו צריך לנחש מילה בת 5 אותיות. כל פעם שניחשתם אות במקום הנכון היא מסומנת בצבע ירוק, כל פעם שניחשתם אות במקום הלא נכון היא מסומנת בצבע כתום, ואות שלא צריכה להיות במילה נשארת אפורה. וורדל מראה את כל המילים שניחשתם כבר עם האותיות לפי הצבעים, ומספק שורה ריקה לניחוש הבא.
במשחק המקורי של ג'וש כולם מקבלים את אותה מילה (היא מתחלפת כל יום), ויש הגבלה על מספר הניחושים. שני מאפיינים שאני מוותר עליהם בגירסת החיקוי כדי לשמור על הקוד פשוט ובצד לקוח בלבד. אני ממליץ לשחק קצת במשחק המקורי לפני שממשיכים לקרוא את הפוסט.
## קומפוננטת עזר ReadOnlyWordInput
קומפוננטת העזר הראשונה שנכתוב אחראית על הצגת ניחוש של משתמש עם סימוני האותיות בצבעים. היא תקבל את המילה שהמשתמש ניחש ואת המילה הסודית נכונה בתור props, ותחשב לכל אות מה הצבע שמתאים לה. בשביל לתת עיצוב שונה לכל אות אני כותב כל אות ב div משלה. ככה נראה הקוד:
function ReadOnlyWordInput(props) {
const { word, correctWord } = props;
function classes(letterIndex) {
let res = "letter";
if (word.chart(letterIndex) === correctWord.charAt(letterIndex)) {
res += " correct";
} else if (correctWord.includes(word.charAt(letterIndex))) {
res += " misplaced";
}
return res;
}
return (
<div>
{_.range(5).map((i) => (
<div className={classes(i)} key={i}>
{word.charAt(i)}
</div>
))}
</div>
);
}
נשים לב שב JavaScript יש שתי דרכים לקבל אות במחרוזת לפי אינדקס: אפשר להשתמש בסוגריים מרובעים או בפונקציה charAt. ההבדל הוא ש charAt מחזיר מחרוזת ריקה אם המחרוזת המקורית קצרה יותר מהאינדקס שביקשתם (בעוד שסוגריים מרובעים מחזירים את הערך undefined).
## קומפוננטת עזר WordInput
הקומפוננטה הכי מסובכת בדמו הזה היא קומפוננטת העזר WordInput שאחראית על קליטת המילה מהמשתמש. בגלל שחשוב לנו להסתכל על המילה בתור אוסף של אותיות גם קומפוננטת הקלט מחלקת את המילה לאותיות ומייצרת אלמנט input נפרד לכל אות. בשביל שזה יעבוד היה צריך לנהל את מעבר הפוקוס בין אלמנטי ה input השונים, וגם לדאוג לזה שכל אחד יעדכן רק את האות שהוא אחראי עליה.
בשביל הכתיבה למחרוזת במקום שמתאים לאינדקס של ה input שעכשיו נמצאים עליו הוספתי פונקציית עזר קטנה בשם replaceAt, שמקבלת אינדקס וכותבת אליו:
function replaceAt(str, index, replacement) {
return (
str.padEnd(5).substr(0, index) +
replacement +
str.substr(index + replacement.length)
);
}
מחרוזות ב JavaScript הן Immutable, ולכן "לכתוב" לאינדקס במחרוזת אומר לייצר מחרוזת חדשה שבה רק האות שאליה אנחנו כותבים תהיה שונה. השימוש ב padEnd מתמודד עם מצב שמנסים לכתוב למחרוזת קצרה יותר מהאינדקס אליה ניסינו לכתוב.
ועכשיו שיש לנו את הפונקציה אפשר להמשיך לקוד של הקומפוננטה עצמה שבגדול מציירת 5 תיבות קלט ומנהלת את מעברי הפוקוס ביניהן:
function WordInput(props) {
const { onSubmit } = props;
const [word, setWord] = useState("");
const [letter, setLetter] = useState(0);
const el = useRef(null);
useEffect(() => {
el.current.querySelectorAll(".letter")[letter].focus();
}, [letter]);
return (
<div ref={el}>
{_.range(5).map((i) => (
<input
className="letter"
key={i}
onFocus={() => setLetter(i)}
onKeyDown={(e) => {
if (e.key === "Enter") {
// ENTER Key
return onSubmit(word);
} else if (e.key === "Backspace") {
setLetter((l) => Math.max(l - 1, 0));
setWord((word) => replaceAt(word, i - 1, " "));
} else if (e.key.match(/^[a-zA-Z]$/)) {
setWord((word) => replaceAt(word, i, e.key.toLowerCase()));
setLetter((l) => Math.min(l + 1, 4));
}
}}
value={word.charAt(i)}
pattern="[a-z]"
/>
))}1 419
# יאמל הוא לא חבר
הפופולריות של דוקר וקוברנטיס הביאה לתודעה הציבורית גם את פורמט קבצי ההגדרות yaml, שזה קיצור ל Yaml Ain't Markup Language (בגירסה המקורית זה היה yet another markup language).
יאמל נועד להיות קריא לבני אדם ובגלל זה מתכנתים אוהבים להשתמש בו בקבצי קונפיגורציה. הבעיה בעבודה עם יאמל שהחוקים שלו הרבה יותר מסובכים מהאינטואיציה ולכן כשאנשים שלא מכירים את החוקים מתחילים לכתוב קל ליפול לבורות.
הנה דוגמה אחת לבור כזה שפגשתי לאחרונה - הקוד הבא ב docker-compose.yml מפעיל ubuntu שידפיס את הטקסט yes we can:
version: "3.9"
services:
app:
image: ubuntu
command: bash -c "echo $$text"
environment:
text: yes we can
אבל אם נשנה את הטקסט ונישאר רק עם המילה yes:
version: "3.9"
services:
app:
image: ubuntu
command: bash -c "echo $$text"
environment:
text: yes
אז הפלט משתנה בהתאמה והופך למילה הבודדת true.
הסיבה לשינוי היא שלמרות שאין חובה להשתמש במרכאות סביב מחרוזות ביאמל, בכתיבה בלי מרכאות הקוד שמפענח את היאמל צריך להבין לבד מה טיפוס הנתונים של הערך. במפתח image הערך ubuntu יהיה מחרוזת, וגם הערך של משתנה הסביבה היה מחרוזת כשהוא החזיק את הטקסט yes we can. אבל, המילה yes מפוענחת בתור ערך בוליאני ולכן מומרת לערך הבוליאני true.
הסקרנים שביניכם יכולים למצוא את מסמך הכללים המלא של יאמל בקישור: https://yaml.org/spec/1.2.2/, אבל אני לא בטוח שהקריאה בו תעשה לכם חשק לכתוב יותר יאמלים.1 419
<p style="font-size: 13px; color: #717074; font-family: Helvetica,Arial,sans-serif;"><img class="xmprfx_center xmprfx_fixedwidth" style="text-decoration: none; max-width: 292px; height: auto; width: 100%; display: block; border: 0px; margin-left: auto; margin-right: auto;" src="https://tbcdn.talentbrew.com/company/1706/v1_0/img/map-world-large.png" alt="" width="292" align="center" border="0" /></p> <p style="font-size: 13px; color: #717074; font-family: Helvetica, Arial, sans-serif; text-align: center;"><img class="xmprfx_center xmprfx_fixedwidth" style="text-decoration: none; max-width: 130px; height: auto; width: 100%; border: 0px;" src="https://www.freeiconspng.com/uploads/dhl-icon-12.png" alt="" width="130" align="center" border="0" /></p> <center style="font-size: 15px; line-height: 1.4;"></center> --b1_290fd56e329304789de69abf5b9ed389--אני עובר שורה שורה על הדברים הכי חשודים. כבר בשורה הראשונה אנחנו רואים כותרת שמתיחסת לדומיין בהונגריה:
Return-Path: <support@letoltokozpont.silverpc.hu>
וזה ממשיך עם:
X-Spam-source: IP='84.21.7.29', Host='noreverse', Country='HU', FromHeader='hu',
MailFrom='hu'
אין סיבה שדואר ישראל ישלחו מייל דרך שרת בהונגריה. אחרי זה יש כמה דברים שאני לא מבין ואז אני מגיע ל:
Authentication-Results: mx1.messagingengine.com;
dkim=invalid (public key: not available, 0-bit key sha256)
את dkim אני מכיר. זה מנגנון שמאפשר למערכות ששולחות דואר אלקטרוני לוודא שהדואר אכן הגיע מהדומיין שממנו הוא טוען שהוא הגיע, במקום לדוגמה שמישהו סתם השאיר מעטפה על השרת וקיווה לטוב. הוא עושה את זה באמצעות שימוש בחתימה דיגיטלית והאימות שלו דורש בדיקה של מפתח ציבורי שרשום בדומיין שממנו כביכול המייל נשלח. תשוו את זה ל dkim בהודעה אמיתית מדואר ישראל:
Authentication-Results: mx.google.com;
dkim=pass header.i=@postil.com header.s=post1 header.b="PTXUV/e6";
spf=pass (google.com: domain of noreply@postil.com designates 68.232.156.189 as permitted sender)
וקל לראות את ההבדל בין ה invalid בדואר המזויף לבין אישור הדומיין עם הסיומת postil.com בדואר האמיתי.
אני מקווה שנתתי לכם כאן מספר כלים לזהות דואר מזויף ורמאויות רשת. צריך להגיד - הרבה רמאויות רשת שניתקל בהן יהיו מתוחכמות בהרבה מהדוגמה פה בפוסט. הדבר שהכי יוצר אמון ברמאויות רשת זה פרטים מזהים, וזאת לדעתי החשיבות של שמירה על פרטיות ברשת: ככל שלתוקפים יש יותר פרטים מזהים עלינו, כך יהיה להם יותר קל לשלוח הודעות מטורגטות שיגרמו לנו להאמין למסר ובאמת לשלם לאותו נוכל.
בדוגמה שלנו אם ההודעה היתה בעברית תקינה, כוללת מספר משלוח אמיתי שאני באמת מחכה לו, מציינת את השם שלי והכתובת שלי ומתי הזמנתי את המוצר, אז היה הרבה יותר סיכוי שהייתי נופל בפח הזה.1 419
spf=none smtp.mailfrom=support@letoltokozpont.silverpc.hu
smtp.helo=vps.silverpc.hu
X-ME-VSCause: gggruggvucftvghtrhhoucdtuddrgedvvddrgedugdeijecutefuodetggdotefrodftvf
curfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdpuffr
tefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecuogfuuhhsphgvtghtkfhmgh
ffohhmrghinhculdeftddmnecujfgurhepvffufffhkfggtgfgsegrkehjphdttdejnecu
hfhrohhmpefkshhrrggvlhcuphhoshhtuceoshhuphhpohhrtheslhgvthholhhtohhkoh
iiphhonhhtrdhsihhlvhgvrhhptgdrhhhuqeenucggtffrrghtthgvrhhnpeekffefkefg
ieffueehffevtdfgfffggeejkeektdegieejkedttdetgfektdetgeenucffohhmrghinh
epuhhmrggvnhhtvghrphhrihhsvgdrnhgvthenucfkphepkeegrddvuddrjedrvdelnecu
vehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehinhgvthepkeegrddvuddrjedrvd
elpdhhvghlohepvhhpshdrshhilhhvvghrphgtrdhhuhdpmhgrihhlfhhrohhmpeeoshhu
phhpohhrtheslhgvthholhhtohhkohiiphhonhhtrdhsihhlvhgvrhhptgdrhhhuqe
X-ME-VSScore: 30
X-ME-VSCategory: clean
X-ME-CSA: none
Received-SPF: none
(letoltokozpont.silverpc.hu: No applicable sender policy available)
receiver=mx1.messagingengine.com;
identity=mailfrom;
envelope-from="support@letoltokozpont.silverpc.hu";
helo=vps.silverpc.hu;
client-ip=84.21.7.29
Received: from vps.silverpc.hu (unknown [84.21.7.29])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
(No client certificate requested)
by mx1.messagingengine.com (Postfix) with ESMTPS
for <info@tocode.co.il>; Mon, 31 Jan 2022 09:38:30 -0500 (EST)
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=letoltokozpont.silverpc.hu; s=mail;
h=Content-Transfer-Encoding:Content-Type:MIME-Version:Message-ID:From:Date:Subject:To; bh=ZqgTTpzfQAWUD0xYG177cmhdpkV33R1FmY/uaEr5sjo=;
b=UqLOs+BkYvMc9UVA7H0FX5ZoqnzgFQQCrwpPekZ5vLutmgGcVIz1P2E7QdCvh3QDnEb3A8IwH0KOBN0KAilKkTVJ1cdGK5Po2Mvn/FvyLs0Y+M00nkauVk4LCzzYTtYBbC5NT3hLmUY+W8IAPbIP4+PiqmYzLsYXd5haTh0s93I=;
Received: from admin by vps.silverpc.hu with local (Exim 4.84_2)
(envelope-from <support@letoltokozpont.silverpc.hu>)
id 1nEXp6-00032X-W1
for info@tocode.co.il; Mon, 31 Jan 2022 14:38:29 +0000
To: info@tocode.co.il
Subject: =?UTF-8?B?157XmdeT16Ig157Xp9eV15XXnyDXotecINek16jXmdeY15nXnSDXkNep16gg?= =?UTF-8?B?16DXqdec15fXlSDXkdeQ157Xptei15XXqiDXk9eV15DXqCDXqdec15nXl9eZ?= =?UTF-8?B?150=?=
X-PHP-Originating-Script: 1001:leafv4.php
Date: Mon, 31 Jan 2022 14:38:28 +0000
From: Israel post <support@letoltokozpont.silverpc.hu>
Message-ID: <290fd56e329304789de69abf5b9ed389@letoltokozpont.silverpc.hu>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="b1_290fd56e329304789de69abf5b9ed389"
Content-Transfer-Encoding: 8bit
This is a multi-part message in MIME format.
--b1_290fd56e329304789de69abf5b9ed389
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
לא ניתן לשלוח את החבילה שלך היום עקב עמלות שחרור ממכס נוספות. הוא יימסר מיד עם תשלום העלות (10.56 שקלים)
לשלם עבור החבילה שלי
--b1_290fd56e329304789de69abf5b9ed389
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
<p style="text-align: left;"><img class="xmprfx_center xmprfx_fixedwidth" style="text-decoration: none; max-width: 227px; height: auto; width: 100%; display: block; border: 0px; margin-left: auto; margin-right: auto;" src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/DHL_Logo.svg/800px-DHL_Logo.svg.png" alt="" width="227" align="center" border="0" /></p>
<p style="text-align: left;"> </p>
<center style="font-size: 15px; line-height: 1.4;"></center>
<p style="text-align: center;">לא ניתן לשלוח את החבילה שלך היום עקב עמלות שחרור ממכס נוספות. הוא יימסר מיד עם תשלום העלות (10.56 שקלים)</p>
<p style="text-align: center;"><a href="https://service.israelpost.co.il.umaenterprise.net/" target="_blank" rel="noopener">לשלם עבור החבילה שלי</a></p>1 419
by mailmx.nyi.internal (Postfix) with ESMTP id 4340D66016D
for <info@tocode.co.il>; Mon, 31 Jan 2022 09:38:32 -0500 (EST)
Received: from mx1.messagingengine.com (localhost [127.0.0.1])
by mx1.messagingengine.com (Authentication Milter) with ESMTP
id 1F529134C53;
Mon, 31 Jan 2022 09:38:32 -0500
ARC-Seal: i=1; a=rsa-sha256; cv=none; d=messagingengine.com; s=fm2; t=
1643639912; b=gOCji1cbMYdvVrCcU6uWH3CgFFjSAycnvocZ/REheC2jEm05nU
1Tq5GnMb0m6IPvqLflnrhDVyWgEpPfilzOV4kpd2+sYGFZWNHKcwWb7jGiDUP9R3
TtpXTpFlX4M4BCgVINCvZgydBP1tui385QfGkYC7S+tTYTaDAPeMHBWbeoablAgS
BHgkczQ7rSAa2QrtqGx9Y2ig6SI3enogOsQu1m6nEIoFH0KGGH+EU6TK4XSxEmBu
NU03ZO55q+v+FqX7t7D8FzFb+eMVtg4ZCbkm0SgAENdoyDlv1pj4xH4qJVxwYjWe
vWnZrjnuAy6A/Ncz+WodfbHCICh7Kp4RqilQ==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=
messagingengine.com; h=to:subject:date:from:message-id
:mime-version:content-type:content-transfer-encoding; s=fm2; t=
1643639912; bh=ZqgTTpzfQAWUD0xYG177cmhdpkV33R1FmY/uaEr5sjo=; b=P
oZ9eav23+IDhhisoI3kTgvB4AKv3jdgn1cradnh+s4GBkeBSEVpb6WYaqP9OVr9S
y7FirUot7b8VepI3jza/1jngMe4ZxIT4wsq1yW/kdK6mvq9La3VLH/Bhfnr0H43b
T7e36HoQPWzG12AhEdRif3L7SnILgVEq+mex9ODOdu08P+b0VyK06mf0DfYyd2PJ
WOe8177RG5NeIoiHc8mtH1cMwzbs/VtpLaDTvQMCE2mety76iIQkpzrVTuhP56wW
/CN3UYkE9E3fr9ywBzQMQb/8WsH7WNXgS8do9R3TrUlytH/rG+hME6EcZ4x/KDzx
2blgfCqkeQs0LVFjjK9Yw==
ARC-Authentication-Results: i=1; mx1.messagingengine.com;
x-csa=none;
x-me-sender=none;
x-ptr=fail smtp.helo=vps.silverpc.hu policy.ptr="";
bimi=skipped (DMARC did not pass);
arc=none (no signatures found);
dkim=invalid (public key: not available, 0-bit key sha256)
header.d=letoltokozpont.silverpc.hu
header.i=@letoltokozpont.silverpc.hu header.b=UqLOs+Bk
header.a=-sha256 header.s=mail x-bits=0;
dmarc=none policy.published-domain-policy=none
policy.applied-disposition=none policy.evaluated-disposition=none
(p=none,d=none,d.eval=none) policy.policy-from=p
header.from=letoltokozpont.silverpc.hu;
iprev=fail smtp.remote-ip=84.21.7.29
(Error NXDOMAIN looking up 84.21.7.29 PTR,NOT FOUND);
spf=none smtp.mailfrom=support@letoltokozpont.silverpc.hu
smtp.helo=vps.silverpc.hu
X-ME-Authentication-Results: mx1.messagingengine.com;
x-aligned-from=pass (Address match);
x-return-mx=warn header.domain=letoltokozpont.silverpc.hu
policy.org_domain=silverpc.hu policy.is_org=no policy.mx_error=NOERROR
(A Records found: 172.67.175.58,104.21.31.64)
(AAAA Records found: 2606:4700:3035:0:0:0:6815:1f40,2606:4700:3035:0:0:0:ac43:af3a);
x-return-mx=warn smtp.domain=letoltokozpont.silverpc.hu
policy.org_domain=silverpc.hu policy.is_org=no policy.mx_error=NOERROR
(A Records found: 172.67.175.58,104.21.31.64)
(AAAA Records found: 2606:4700:3035:0:0:0:ac43:af3a,2606:4700:3035:0:0:0:6815:1f40);
x-tls=pass smtp.version=TLSv1.2 smtp.cipher=ECDHE-RSA-AES256-GCM-SHA384
smtp.bits=256/256;
x-vs=clean score=30 state=0
Authentication-Results: mx1.messagingengine.com;
x-csa=none;
x-me-sender=none;
x-ptr=fail smtp.helo=vps.silverpc.hu policy.ptr=""
Authentication-Results: mx1.messagingengine.com;
bimi=skipped (DMARC did not pass)
Authentication-Results: mx1.messagingengine.com;
arc=none (no signatures found)
Authentication-Results: mx1.messagingengine.com;
dkim=invalid (public key: not available, 0-bit key sha256)
header.d=letoltokozpont.silverpc.hu
header.i=@letoltokozpont.silverpc.hu header.b=UqLOs+Bk
header.a=-sha256 header.s=mail x-bits=0;
dmarc=none policy.published-domain-policy=none
policy.applied-disposition=none policy.evaluated-disposition=none
(p=none,d=none,d.eval=none) policy.policy-from=p
header.from=letoltokozpont.silverpc.hu;
iprev=fail smtp.remote-ip=84.21.7.29
(Error NXDOMAIN looking up 84.21.7.29 PTR,NOT FOUND);1 419
# פישינג פישינג פישינג
המייל הזה הגיע היום-
כותרת: מידע מקוון על פריטים אשר נשלחו באמצעות דואר שליחים
תוכן המייל: לא ניתן לשלוח את החבילה שלך היום עקב עמלות שחרור ממכס נוספות. הוא יימסר מיד עם תשלום העלות (10.56 שקלים).
ואז היה לינק לתשלום עבור החבילה שלי.
בואו נדבר עליו.
## קודם מה שקופץ לעין
סימן חשוד ראשון הוא שהמייל מגיע בלי שום פניה אישית: הוא לא אומר לי איזו חבילה, למי מיועדת, מה מספר משלוח וכן הלאה.
סימן חשוד שני הוא שגיאות הדקדוק. חבילה בעברית היא נקבה.
סימן חשוד שלישי הוא כתובות ה"מאת" ו"אל", הדואר הגיע מאת support@letoltokozpont.silverpc.hu שלא נראה בכלל כמו כתובת של הדואר, ויועד לכתובת אימייל שלי שאני לא משתמש בה להזמנת משלוחים. שימו לב שבג'ימייל אתם יכולים להוסיף סימן פלוס ואחריו מספר ואז אתם מקבלים כתובת אחרת שמגיעה לאותה תיבה, כלומר כל הכתובות האלה:
foo@gmail.com
foo+1@gmail.com
foo+2@gmail.com
foo+3@gmail.com
foo+4@gmail.com
יגיעו לאותה תיבת ג'ימייל. אפשר להשתמש בזה כדי לזהות שירותים מסוימים שאתם ניגשים אליהם, למשל אפשר להזמין משלוחים תמיד מהכתובת שמסתיימת במספר 17, ואז אם מישהו פונה לכל כתובת מייל אחרת שלכם אתם יודעים שהוא לא מרשות הדואר.
## דף התשלום
אנחנו כבר די בטוחים שההודעה מזויפת ולכן זה הזמן פשוט למחוק אותה. נכון, יש האקרים מאוד מתוחכמים שיכולים לפרוץ לכם למחשב אפילו דרך שליחת הודעה (רק מזה שפתחתם את המייל), אבל יש הרבה יותר האקרים שיצטרכו שתלחצו על לינק כדי לעשות נזק.
אני לקחתי בשבילכם ובשביל המשחק את הסיכון אז בואו נמשיך.
לחיצת כפתור ימני על הקישור ו"העתק קישור" מגלה לי שהטקסט "לשלם עבור החבילה שלי" היה לוקח אותי לעמוד הבא:
https://service.israelpost.co.il.umaenterprise.net/
הטריק כאן הוא מאוד פשוט - יש כתובת שההתחלה שלה נראית כמו דואר ישראל אבל הסיומת מראה לנו שזה דומיין אחר ולא קשור לדואר. צריך לזכור שכל אחד יכול לשנות את החלק השמאלי של כתובת אינטרנט אבל זה החלק הימני שקובע, וצריך תמיד לקרוא את הכתובות האלה מימין לשמאל.
הדומיין umaenterprise.net לא נראה כמו משהו שקשור לדואר. בכניסה לאתר הבית בדומיין אני מגלה ש uma זה ספק טלקום הודי, שוב קשה לראות קשר לדואר ישראל.
ככל הנראה מדובר במערכת שמאפשרת לאנשים ליצור אתרים עם איזשהו סאבדומיין לבחירתם וככה אותו תוקף יצר את הכתובת של אתר הפישינג.
אפילו אם התפתיתי עד לכאן והגעתי לאתר התשלום חשוב לשים לב לכמה מאפיינים שם:
1. הדומיין שמופיע בשורת הכתובת של הדפדפן מסתיים ב umaenterprise.net.
2. נכון, יש לוגו של דואר ישראל, אבל הטופס באנגלית וגם הלוגו באנגלית. המייל שהם שלחו לי היה בעברית.
3. הם מבקשים ממני מספר טלפון ישראלי אבל בכל זאת מציינים במפורש את הקידומת של ישראל 972 בטופס.
4. אין בטופס שום פרטים מזהים על המשלוח שלי.
מיטיבי לכת יוכלו ללחוץ View Page Source על אותו העמוד ולראות שאין שם הפניה לחברת כרטיסי אשראי ובמקום זה הטופס מפנה לקובץ php פנימי באתר. בתשלום בכרטיס אשראי תמיד היינו מצפים לראות את פרטי האשראי נשלחים לספק תשלומים מוכר.
## כותרות ותוכן ההודעה
בתוכנת הדואר שלי יש כפתור "Show Raw Message". בג'ימייל התווית היא Show Original, בכל מקרה, זה מה שקיבלתי כשלחצתי עליו:
Return-Path: <support@letoltokozpont.silverpc.hu>
Received: from compute1.internal (compute1.nyi.internal [10.202.2.41])
by sloti45n26 (Cyrus 3.5.0-alpha0-4585-ga9d9773056-fm-20220113.001-ga9d97730) with LMTPA;
Mon, 31 Jan 2022 09:38:33 -0500
X-Cyrus-Session-Id: sloti45n26-1643639913-2945080-2-12629018722928771173
X-Sieve: CMU Sieve 3.0
X-Spam-known-sender: no
X-Spam-sender-reputation: 500 (none)
X-Spam-score: 3.0
X-Spam-hits: DCC_CHECK 1.1, HTML_IMAGE_ONLY_20 0.7, HTML_MESSAGE 0.001, ME_NOAUTH 0.01,
ME_SENDERREP_NEUTRAL 0.001, RDNS_NONE 1.274, SPF_HELO_NONE 0.001,
SPF_NONE 0.001, T_REMOTE_IMAGE 0.01, LANGUAGES unknown, BAYES_USED none,
SA_VERSION 3.4.2
X-Spam-source: IP='84.21.7.29', Host='noreverse', Country='HU', FromHeader='hu',
MailFrom='hu'
X-Spam-charsets: subject='UTF-8', plain='UTF-8', html='UTF-8'
X-Resolved-to: ynonperek@fastmail.com
X-Delivered-to: info@tocode.co.il
X-Mail-from: support@letoltokozpont.silverpc.hu
Received: from mx1 ([10.202.2.200])
by compute1.internal (LMTPProxy); Mon, 31 Jan 2022 09:38:33 -0500
Received: from mx1.messagingengine.com (localhost [127.0.0.1])1 419
# הזמנה לוובינר: Next.JS
אחת השאלות הכי נפוצות כשאנשים מתחילים לעבוד עם React ו Node היא איך לחבר את שני הדברים יחד ולהעלות לאוויר אפליקציה מלאה.
מצד אחד הגישה "הפשוטה" לחיבור ריאקט לקוד צד שרת עובדת גם בעבודה מול Node: אנחנו בונים API באקספרס שיודע לטפל ב CORS, מתוך ריאקט שולחים בקשות Ajax ל API הזה ומציגים את התשובות. אפליקציית ריאקט מוגשת בצורה עצמאית מתוך nginx, שמשמש גם בתור Reverse Proxy ל Express.
אבל אני לא בטוח שהגישה הזאת מספיק פשוטה או מספיק מומלצת. בין הבעיות שלה:
1. אין תמיכה ב Server Side Rendering.
2. אין דרך קלה לשתף קוד בין אפליקציית הריאקט לאפליקציית ה Node.JS.
3. צריך לבנות לבד מנגנוני Build לשתי הסביבות.
4. צריך להתארגן על שרת ו nginx, ולתחזק אותו.
ורסל (באנגלית vercel) היא חברה שמנסה לתת לכם בדיוק את הדבק שחסר בין המערכות, בתוך חווית פיתוח נוחה ומהירה. הם בנו סביבת עבודה בשם Next.JS שמחברת בין ריאקט ל Node בצורה חלקה לגמרי, והם גם מספקים מערכת אירוח קוד בענן עם חשבון חינמי למתחילים כדי שתוכלו להעלות את הקוד שכתבתם בלחיצת כפתור לענן.
בוובינר השבוע נדבר על אותו Next.JS ונראה איך להשתמש בו כדי לחבר בין React ל Node.JS, וליישומי ענן נוספים כולל יישומיים מספקים אחרים. את הוובינר אני אעביר יחד עם ניר פריזיאן, ויחד אנחנו נראה:
1. איך לבנות יישום Next.JS מאפס.
2. איך להריץ אותו במצב פיתוח.
3. איך Next.JS מחברת בין קוד ריאקט לקוד Node.JS, איך בונים API Endpoints ואיך מפעילים Server Side Rendering.
4. איך להעלות את היישום שכתבנו לענן כך שיהיה זמין מכל מקום.
5. איך לחבר את היישום לספק בסיסי נתונים (אנחנו נעבוד עם פיירבייס) כדי לשמור מידע משתנה.
לפרטים והצטרפות מוזמנים להיכנס לדף הוובינר בקישור:
https://www.tocode.co.il/workshops/111
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
