הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

ואם המידלוור לא ירוץ?

24/03/2025

באג מעניין במנגנון המידלוורים של next.js איפשר לתוקפים לדלג על המידלוור באמצעות הזרקת כותרת מיוחדת לבקשה. לא ניכנס לפרטים הטכניים אבל כן שווה לשים לב לסיפור מנקודת מבט של אבטחת מידע. ביישומים רבים מנגנון ניהול המשתמשים וחסימת גישה לנתיבים מסוימים מתבצע במידלוור, לפני שהבקשה מגיעה לקוד שאחראי על הטיפול בה. יש פה ראייה ארכיטקטונית של הפרדה בין הקוד "שמנהל גישה" לקוד "שמבצע את הפעולה". לדוגמה במדריך של auth0-next מצאתי את הבלוק הבא:

import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge";

export default withMiddlewareAuthRequired();

export const config = {
  matcher: ["/protected", "/admin"],
};

הקוד מגדיר שגישה לנתיבי protected ו admin מותרת רק למשתמשים מאומתים. אתם רואים את הבעיה נכון? אם המידלוור לא רץ הגישה פתוחה לכולם.

נשווה את זה לעיקרון "בודקים צמוד לפעולה המסוכנת" של פיתוח קוד מאובטח. בעבודה נכונה אנחנו רוצים לבדוק הרשאות בתוך דף ה admin, ואפילו בתוך הפונקציה שמושכת מידע של מנהל מבסיס הנתונים. שימו לב למימוש הדוגמה הבא מתוך אותו מדריך:

export const getUserProfileData = async (): Promise<Claims> => {
  const session = await getSession();

  if (!session) {
    throw new Error(`Requires authentication`);
  }

  const { user } = session;

  return user;
};

פונקציה כזאת לא תאפשר למשוך נתונים רגישים, אפילו אם כל הקוד שלפניה בוטל. היא בעצמה לוקחת את נתוני המשתמש ומוודאת שהמשתמש מחובר. במערכת עם מנגנון הרשאות פונקציה זו תוכל גם לוודא שלמשתמש שמחובר יש הרשאות לבצע את הפעולה שהיא רוצה לבצע, ורק אז תריץ את הקוד המסוכן.

קוד מאובטח לא מניח שמישהו לפניו בדק הרשאות - הוא בודק הרשאות צמוד ובדיוק לפני הפעולה המסוכנת, כדי שאי אפשר יהיה "לדלג" על בדיקת ההרשאות ולהגיע רק לקוד הרגיש.

טיפ JavaScript - שימוש ב matchMedia במקום resize

23/03/2025

פקודת matchMedia היא טריק חמוד שמאפשר לנו לקבל אירוע כל פעם ש"התאמת המסך ל Media Query" מסוים משתנה. זה מנגנון הרבה יותר יעיל מאירועי resize כי רוב הזמן מה שמעניין אותנו זה לא שמשתמש שינה את גודל החלון אלא שגודל החלון נהיה מספיק קטן כדי להתיחס אליו כמו לטלפון, או מספיק גדול כדי להציג עליו יותר מידע. זאת בעצם גירסת ה JavaScript של Media Queries ב CSS.

הנה דוגמה קצרה ל Hook ריאקטי שבודק אם אנחנו על מכשיר מובייל:


const MOBILE_BREAKPOINT = 768

export function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
    const onChange = () => {
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    }
    mql.addEventListener("change", onChange)
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    return () => mql.removeEventListener("change", onChange)
  }, [])

  return !!isMobile
}

כלים שקופים

22/03/2025

אם העתיד שלנו הוא עוזרי AI לכתיבת קוד אז כדאי שנחשוב שוב על שפות הפיתוח והספריות בהם אנחנו משתמשים. הנה כמה דברים שלמדתי במשחקים עם AI בשבוע האחרון:

  1. מאוד קל לו לכתוב משחק סנייק, מאוד קשה להשתמש נכון בספריות מ npm. זה קורה כי משחק סנייק משתמש רק ביכולות של הדפדפן כלומר מידע שהוא התאמן עליו, ואילו ספריות ב npm משנות את הממשק כל שלושה חודשים ולכן יש יותר מדי קונטקסט.

  2. מאוד קל לו לכתוב שאילתת SQL, הרבה יותר קשה לכתוב קוד ORM.

  3. מאוד קל לו לכתוב תבנית SAM לארכיטקטורת Serverless על AWS, הרבה יותר קשה לו לתקן סקריפט התקנה לשרת VPC.

  4. מאוד קל לו לכתוב עמוד חדש לאתר, הרבה יותר קשה כשהעמוד צריך להשתמש בקומפוננטות קיימות מהמערכת שאולי לא מתועדות מספיק טוב.

  5. מאוד קל לו לכתוב בדיקת End To End לפי רשימת תרחישים שאני מתאר, הרבה יותר קשה להקים תשתית לבדיקות יחידה ולהחליט איזה Mock-ים לעשות ואיך צריך לארגן אחרת את הקוד הספציפי שלי כדי שיהיה קל לכתוב אותו.

מה שמשותף לכל המקרים כאן, ולעבודה עם AI באופן כללי הוא הקו שבין הספציפי לכללי - ככל שמדובר בלקחת "קוד מספר לימוד" ולהדביק אותו לפרויקט תוך ביצוע התאמות נדרשות דברים יעבדו ויפתיעו לטובה. ברגע שאנחנו מגיעים ל Refactoring או לקבלת החלטות העסק מסתבך. זה אומר שאם אנחנו רוצים להרוויח מ AI כדאי לחפש את המקומות בהם חלון הקונטקסט קטן (צריך פחות טוקנים בקלט כדי לפתור את הבעיה) והמשמעות של הטוקנים יציבה. אז SQL זה מצוין כי כמו שכתבתי את השאילתה ככה היא הולכת ל DB, ו ORM זה מסובך כי הגדרות הקשרים בין המודלים ממוקמות בקובץ נפרד או כמה קבצים או שילוב בין קבצים ל DB.

רוצים להרוויח מ AI? נסו להזיז את הפרויקט שלכם לכיוון כלים שקופים וסטנדרטיים. אבסטרקציות מתוחכמות וספציפיות למערכת שלנו יוצרות עכשיו עלות חדשה ואולי לא שווות את המאמץ.

תבנית לפיתוח Chat Bot ב Next.JS

21/03/2025

לא משנה כמה חשבתם שלכם קשה עם כל הספריות שמשתנות כל הזמן בעולם ה Full Stack, ל AI זה הרבה יותר קשה. אולי בגלל זה הם מצליחים לבנות משחק סנייק בפחות מדקה, אבל רק ננסה משהו שצריך ספריות מ npm והוא הולך לאיבוד בגירסאות. וזה חבל כי עדיין יש המון דברים ב npm שיכולים לחסוך לנו זמן פיתוח ובאגים. הדוגמה שלנו היום היא ספריות ה ai של Vercel.

המשך קריאה

מחזיק אצבעות

20/03/2025

החוויה הכי משמעותית שלי בעבודה עם כלי AI.

מחזיק אצבעות.

אני כותב פרומפט, ואז מחזיק אצבעות שהתשובה תהיה כמו שרציתי. ההמתנה לתשובה היא חלק מהקסם, אתה יודע שה AI יבוא עם תשובה, ושזה יהיה קצת יותר מהר ממה שאתה תמצא את התשובה לבד. אז אולי שווה לחכות. ואם הוא צודק זאת ממש חגיגה.

אבל כשהוא טועה? כשהוא טועה צריך לבחור, האם לשאול שאלת המשך, להתחיל מההתחלה או לרדת מזה ולחשוב לבד. שאלת המשך יכולה להצליח, להתחיל מחדש עם פרומפט חדש גם יכול לעבוד, ולחשוב לבד? עד שהגענו עד לפה.

ואז אני נכנס ללופ - פרומפט, תשובה לא אפויה, תיקון. פרומפט, המון קוד גרוע שלא עובד, פרומפט המשך עם הודעת השגיאה. ושוב. ושוב.

יש לי חבר שכל פעם שאני מציע לו ספריית קוד פתוח שבדיוק תפתור לו איזה בעיה ספציפית הוא מסתכל עליי מלמעלה ומסנן "עזוב אותך, עד שאני אבין איך זה עובד אני כבר אכתוב לבד משהו יותר טוב". ובאמת לקח לנו המון זמן לפתח תיעוד מספיק טוב לספריות קוד פתוח ואינטואיציה מספיק טובה לגבי מתי כדאי להשתמש בהן (ואנחנו עדיין מתקשים עם זה). אולי גם ל AI צריך לתת זמן. כרגע הרושם הוא שיש דברים שעובדים וחוסכים המון זמן, ויש דברים שלא עובדים ששואבים אותך לשעות, וברור שאם היה אפשר היינו משתמשים ב AI רק לאותם דברים שעובדים. והכי מעניין כרגע - האם בעתיד ה AI יאכל את המתכנתים (כמו ש C החליף את אסמבלי, כמו שטכנולוגיות ווב החליפו אפליקציות דסקטופ) ויישארו רק ארכיטקטים, מנהלי מוצר ופרומפטים?

קשה לדעת. בינתיים אני מחזיק אצבעות.

איך להעלות תמונה (או כל קובץ אחר) ב Next.JS ב 2025

19/03/2025

ריאקט 19 ו next 15 עובדים יחד בהרמוניה כדי לאפשר לנו המתכנתים הפשוטים לכתוב אפליקציות Full Stack במהירות. אחד המקומות שקיבל טיפול באינטגרציה הזו הוא הטפסים. בואו נראה איך עובדים עם טפסים ב 2025 ב React ו Next ובפרט איך להעלות תמונות לשרת.

המשך קריאה

ואז פתחתי את הקוד

18/03/2025

ביקשתי מ Lovable לכתוב אפליקציה שתיקח קישור ל URL ותייצר לי בוחן אוצר מילים על המילים שם, ככה בשביל לתרגל את הספרדית. בשביל שלא יהיה קל מדי ביקשתי ממנו גם שיבקש את הרמה הנוכחית שלי בספרדית ויבנה בוחן על מילים מה URL שמתאימות לרמה שלי. זה לקח לו קצת זמן אבל בסוף הוא הוציא את האפליקציה כאן:

https://colorlingo-quizzy.lovable.app/

אחרי כמה משחקים הרגשתי שהוא קצת מרמה אז הלכתי לקוד כדי למצוא את הפונקציה הבאה שמוציאה את המילים מהלינק:

// This would be populated with real data from the URL and filtered by level
// For now, we're creating dummy words for the custom quiz
const generateDummyWords = (url: string, level: SpanishLevel) => {
  // Extract domain name for demonstration
  let domain = '';
  try {
    domain = new URL(url).hostname.replace('www.', '');
  } catch {
    domain = 'example';
  }

  // These would actually come from scraping and NLP processing
  const allWordPairs = [
    // A1 level words (beginner)
    { english: "hello", spanish: "hola", level: "A1" },
    { english: "goodbye", spanish: "adiós", level: "A1" },
    { english: "thank you", spanish: "gracias", level: "A1" },
    { english: "please", spanish: "por favor", level: "A1" },

    // A2 level words (elementary)
    { english: "welcome", spanish: "bienvenido", level: "A2" },
    { english: "friend", spanish: "amigo", level: "A2" },
    { english: "family", spanish: "familia", level: "A2" },
    { english: "today", spanish: "hoy", level: "A2" },

    // B1 level words (intermediate)
    { english: "website", spanish: "sitio web", level: "B1" },
    { english: domain, spanish: domain, level: "B1" },
    { english: "language", spanish: "idioma", level: "B1" },
    { english: "content", spanish: "contenido", level: "B1" },

    // B2 level words (upper intermediate)
    { english: "experience", spanish: "experiencia", level: "B2" },
    { english: "develop", spanish: "desarrollar", level: "B2" },
    { english: "improve", spanish: "mejorar", level: "B2" },
    { english: "progress", spanish: "progreso", level: "B2" },

    // C1 level words (advanced)
    { english: "fluency", spanish: "fluidez", level: "C1" },
    { english: "proficient", spanish: "competente", level: "C1" },
    { english: "articulate", spanish: "articular", level: "C1" },
    { english: "communicate", spanish: "comunicar", level: "C1" },

    // C2 level words (proficiency)
    { english: "mastery", spanish: "dominio", level: "C2" },
    { english: "nuance", spanish: "matiz", level: "C2" },
    { english: "sophisticated", spanish: "sofisticado", level: "C2" },
    { english: "eloquent", spanish: "elocuente", level: "C2" },
  ];

  // Filter words based on selected level or lower
  const levelOrder = ["A1", "A2", "B1", "B2", "C1", "C2"];
  const levelIndex = levelOrder.indexOf(level);
  const filteredWords = allWordPairs.filter(word => 
    levelOrder.indexOf(word.level as SpanishLevel) <= levelIndex
  );

  // Shuffle the array to make it more random and take 10 words
  return filteredWords
    .sort(() => 0.5 - Math.random())
    .slice(0, 10)
    .map(({ english, spanish }) => ({ english, spanish }));
};

בקיצור הוא לגמרי עבד עליי. אף מילה מהלינק לא מגיעה לבוחן אוצר מילים. (הגירסה השנייה שלו גם לא היתה נכונה אבל מסיבה אחרת).

עכשיו הבאג הזה יחסית נחמד כי קל לזהות אותו, אבל אני חושב שהוא ממחיש את היצירתיות של ה AI, שהיא הכח והחולשה שלו: כשהוא לא יודע הוא לא עוצר לשאול או לחשוב, פשוט משלים קוד מהמאגר האינסופי של קודים שהוא אומן עליהם. מאוד יעיל ל POC-ים קטנים כשאנחנו בתוך הצוות או חוקרים רעיון, הרבה פחות רלוונטי למערכת שצריכה לפגוש משתמשים אמיתיים.

אז מה עושים? כלי AI הם פה כדי להישאר, עם היתרונות והחסרונות שלהם. שימוש נכון בכלים אלה בהחלט ישפר את מהירות הפיתוח שלכם. שימוש לא נכון, גם אם מלהיב באותו רגע, יכול בקלות לגמור לנו אחר צהריים. ביום חמישי שבוע הבא (ה 27.3 ב 20:00) ערן גולדמן-מלכא הזמין אותי לדבר איתו בוובינר על שימוש נכון בכלי AI לפיתוח - נראה שם את lovable, base44, קופיילוט, זנקוד, רדי ועוד המון חברים ואספר מה ואיך עובד עבורי. מוזמנים להצטרף בלינק:

https://calendly.com/aumint/10xdevwith_ai?month=2025-03&date=2025-03-27

זה לקח רק שתי הודעות

17/03/2025

כולם מדברים על base44. חבר אחד עשה איתו משחק כזה וחבר אחר משחק אחר, אז אמרתי יאללה גם אני רוצה לנסות. אחרי שתי הודעות קיבלתי ממנו את התשובה:

However, I don't see an integration available for audio transcription in the current platform. This would significantly limit the tool's usefulness.

אני מכבד את הכנות. כלים אחרים היו יכולים לקחת אותי ב Loop דקות ארוכות עד שהייתי מבין שאנחנו בדרך ללא מוצא. והכנות הזאת היא בדיוק הסיפור של ה AI בפיתוח שאני חושב שלא מספיק מדברים עליו.

כן זה כלי מדהים.

כן אפשר לכתוב קוד יותר מהר.

כן זה מקצר זמנים והופך פרויקטי צד להרבה יותר קלים וכיפים.

אבל גם היום פיתוח מוצר תוכנה (גם פרויקט צד) דורש ידע בתוכנה, לפחות החל מנקודה מסוימת. הדרך המהירה ביותר קדימה היא לא בהכרח זו שמצריכה הכי פחות "לכתוב קוד" אלא זו שמאפשרת לנו לשלב בין קוד ויכולות שלנו לבין המהירות של ה AI. כשאנחנו לא מפחדים לקודד ומוכנים לצלול ולהבין את הקוד של ה AI, אנחנו יכולים לקחת את הרעיונות המהירים שלו ולשלב אותם לתוך מבנים נכונים שמתאימים ליישום שלנו.

וכן אלה בדיוק העקרונות שאנחנו מתרגלים בתוכנית הליווי החדשה שיצרתי. הקבוצה הבאה יוצאת לדרך ביוני ואפשר ללמוד עוד בדף התוכנית פה באתר.

מבט נוסף על Jotai

16/03/2025

כתבתי בעבר על jotai ואני מודה שבאותו זמן הוא לא היה ספריית ניהול הסטייט המועדפת עליי. חלפה שנה וחצי והיום אני כבר הרבה יותר אוהב אותו וממליץ עליו לפרויקטים חדשים. בואו נראה למה.

המשך קריאה

ארבעה דברים שאהבתי בפליירייט (ואחד שאהבתי פחות)

15/03/2025

פליירייט היא ספריית כתיבת בדיקות מקצה לקצה לאפליקציות ווב שמתאימה לפייתון, TypeScript / JavaScript, ג'אווה ודוטנט. בזכות כמה פיתרונות ורעיונות ייחודיים ועוד כמה רעיונות טובים שהם אספו ממקומות אחרים העבודה עם פליירייט מרגישה נכונה מהרגע הראשון. הנה 4 דברים שאני מאוד אהבתי בספריה ואחד שפחות. נתחיל בטובים:

  1. תיעוד - אני יודע זה כאילו לא חלק מהספריה אבל האמת שכשמתחילים לעבוד עם ספריה ומגלים עמודי תיעוד מושקעים שכוללים גם הסבר מפורט, גם וידאו כשצריך וגם מדריכים ו Best Practice אתה כבר מרגיש בידיים טובות.

  2. כתיבת בדיקות באמצעות הקלטה - מתוך שורת הפקודה כתבתי npx playwright test --debug ונכנסתי למצב הדיבאג של הבדיקות, שם היה לי דפדפן שהראה את העמוד וכפתור "הקלטה" שפשוט מתרגם את הפעולות שלי לקוד בדיקה. נכון הוא לא ידע לכתוב את ה expect כי הוא לא ידע למה לצפות, אבל כל השאר עבד ממש בסדר. זו בדיקה לדוגמה שכתבתי לאתר טוקוד באמצעות המקליט שלהם:

test('sign up to receive daily posts', async ({page}) => {
    await page.goto('https://www.tocode.co.il/blog');
    await page.getByRole('textbox', { name: 'you@wherever.you.are' }).click();
    await page.getByRole('textbox', { name: 'you@wherever.you.are' }).fill('ynon@tocode.co.il');
    await page.getByRole('button', { name: 'שלחו לי למייל' }).click();
    await expect(page.getByText('נשלח אליך מייל עם קישור לאישור ההרשמה. יש לפתוח את המייל וללחוץ על הכפתור לאישור')).toBeVisible();
})
  1. תמיכה מלאה בכל הדפדפנים וגדלי המסך - באמצעות הגדרה בקובץ קונפיגורציה הצלחתי מאוד מהר לבדוק על יותר או פחות דפדפנים וגם לבחור אמולטורים לגדלי מסך שונים באמצעות פיצ'ר Device Mode של הדפדפנים. הנה דוגמה לקונפיגורציה:

import { defineConfig, devices } from '@playwright/test'; // import devices

export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
      },
    },
    {
      name: 'Mobile Safari',
      use: {
        ...devices['iPhone 13'],
      },
    },
  ],
});
  1. העדפה לכתיב ה Role - למרות שבתור כותב בדיקה זה יכול לעייף, אבל ההעדפה של פליירייט לכיוון Locators שמשתמשים ב Aria Role עוזרת להדגיש את ה Role של כל דבר באתר ולראות מהר כשהגדרות ה Aria לא נכונות, וזה אפילו לפני שסיימנו לכתוב את הבדיקות. אגב יש להם גם תוסף לבדיקת בעיות נגישות והכל משולב בפנים שימו לב לדוגמה הזו:
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright'; // 1

test.describe('homepage', () => { // 2
  test('should not have any automatically detectable accessibility issues', async ({ page }) => {
    await page.goto('https://your-site.com/'); // 3

    const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); // 4

    expect(accessibilityScanResults.violations).toEqual([]); // 5
  });
});
  1. ומה פחות? אני מצאתי את כתיב ה expect מבלבל במיוחד כשחלק מהבדיקות סינכרוניות ואחרות אסינכרוניות לדוגמה שתי הבדיקות האלה אינן זהות:
await expect(page).toHaveURL('...');
await expect(page.url()).toBe('...');

בראשונה toHaveURL היא בדיקה אסינכרונית שמחכה עד שה URL ישתנה בעקבות לחיצה או הפניה, אבל הבדיקה השניה היא בדיקה מיידית כי page.url היא פונקציה סינכרונית ו toBe היא בדיקה סינכרונית. ה await בשורה השנייה לא עושה כלום כיוון שאין שם שום Promise. בפועל מבנה זה דורש להסתכל על הטיפוסים כל פעם שכותבים בדיקה כדי להבין אם פונקציית הבדיקה היא סינכרונית או אסינכרונית.

סך הכל מאוד אהבתי את פליירייט ואם אתם צריכים ספריית בדיקות End To End לפרויקט אין ספק שמדובר בבחירה טובה ובטוחה.