← כל הפרויקטים

MA-Trip

MA-Trip היא אפליקציית PWA בעברית RTL שמתקינים מהדפדפן למסך הבית: מסלול יומי, רשימת אריזה, תקציב, מפה, מסמכים וזיכרונות עם הקלטות קוליות. היא בנויה מ-13 מסכי מוצר בלי שום שלב build, עובדת גם בלי רשת, ושומרת את המדיה מוצפנת על המכשיר. היא חיה ב-ma-trip.pages.dev.

13
מסכי מוצר
8
טבלאות D1
1,000
אריחי מפה במטמון
מאחורי הקלעים

הצינור: מכשיר ראשון, ענן רק כשצריך

מצב במכשירחנות אחת בזיכרון, נשמרת ל-localStorage
מדיה מוצפנתAES-GCM, יושבת ב-IndexedDB על המכשיר
Service Workerשלד האפליקציה ואריחי המפה נשמרים למצב לא מקוון
Worker בקצהAPI אחד עם sessions והגבלת קצב לפי כתובת
D1 · R2 · KV8 טבלאות, בלובים מוצפנים והגבלות קצב

האפליקציה שלמה גם בלי הענן: הסנכרון הוא שכבה נוספת, לא תנאי להפעלה.

היקף האפליקציה במספרים אמיתיים
מסכי מוצר13 יחידות
טבלאות D18 יחידות
שירותי Cloudflare5 יחידות

ההחלטה שקבעה את כל הארכיטקטורה היא שטיול קורה בדיוק במקומות שבהם אין קליטה. לכן המצב כולו חי במכשיר — חנות אחת בזיכרון שנשמרת ל-localStorage — והענן נכנס רק כשמישהו רוצה לסנכרן עם בן זוג. מכאן נגזרו שאר ההחלטות: ה-Service Worker מחזיק מטמון נפרד לאריחי המפה שלא נמחק בעליית גרסה, כי אריחים שנשמרו לפני הטיסה הם בדיוק מה שצריך לשרוד; תמונות והקלטות מוצפנות ב-AES-GCM לפני שהן יושבות ב-IndexedDB, ורק הבלוב המוצפן עולה לאחסון בענן; וכל רשומה נושאת חותמת זמן, כדי ששני מכשירים שערכו את אותה רשומה יתמזגו לפי העדכני מביניהם במקום למחוק זה את זה. גם מחיקה מסומנת ולא נעלמת, אחרת סנכרון היה מחזיר לחיים פריט שכבר נמחק במכשיר השני.

מצב אחד במכשיר, שלוש שכבות שנשענות עליו
מצב אחד במכשיר
חנות אחת בזיכרון עם מנוי לשינויים, נשמרת ל-localStorage בכל עדכון; כל רשומה נושאת חותמת זמן, וכל מחיקה נשמרת כסימון ולא כהיעלמות
13 מסכי מוצר
בית, מסלול, מפה, אריזה, תקציב, מסמכים, תזכורות, מקומות, זיכרונות ועוד — כולם קוראים מאותה חנות ולכן אף מסך לא מחזיק אמת משלו
מפה שעובדת בלי רשת
Leaflet מעל אריחי OpenStreetMap, עם מטמון אריחים נפרד שמוגבל לאלף אריחים ושורד עליית גרסה
מדיה מוצפנת
תמונות, הקלטות קוליות וסריקות מסמכים מוצפנות ב-AES-GCM ויושבות ב-IndexedDB לפני שבכלל מדובר בהעלאה
סנכרון אופציונלי
Worker בקצה מול 8 טבלאות D1, בלובים מוצפנים ב-R2 והגבלת קצב ב-KV

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

מה זה

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

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

הבעיה

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

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

מה בניתי

בניתי אפליקציה שהמצב שלה חי במכשיר. יש חנות מרכזית אחת שכל המסכים קוראים ממנה, והיא נשמרת ל-localStorage בכל שינוי — כך שסגירת האפליקציה, ניתוק רשת או המראה לא מוחקים כלום.

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

המפה בנויה על Leaflet מעל אריחי OpenStreetMap, עם חיפוש כתובות דרך Nominatim ובלי שום מפתח API. ה-Service Worker שומר את שלד האפליקציה, ובנוסף מחזיק מטמון אריחים נפרד המוגבל לאלף אריחים — שנשאר על כנו גם כשעולה גרסה חדשה, כדי שאריחים שנשמרו מראש לא ייעלמו בדיוק כשצריך אותם.

איך זה עובד

הפרונטאנד הוא React 18 שנטען כספרייה מוכנה מתוך התיקייה המקומית, עם קומפילציה של JSX בדפדפן עצמו. אין bundler, אין node_modules בפריסה ואין שלב build — מגישים קבצים סטטיים, וזהו. המחיר הוא קומפילציה בזמן טעינה; התמורה היא שאפשר לתקן משהו ולפרוס תוך שניות.

הצד השרתי הוא Cloudflare מקצה לקצה: Pages מגיש את האפליקציה, Worker אחד מספק את ה-API, D1 מחזיק שמונה טבלאות (חשבונות, sessions, קישורי כניסה, טיולים, חברי טיול, אינדקס בלובים, אסימוני שיתוף שבוטלו ויומן ביקורת), R2 מחזיק את הבלובים המוצפנים ו-KV אחראי על הגבלת קצב לפי כתובת.

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

הסטאק

React 18.3.1 ו-Leaflet 1.9.4 מתארחים מקומית, קומפילציית JSX בדפדפן, localStorage ו-IndexedDB לאחסון, Web Crypto להצפנה, MediaRecorder להקלטות ו-Service Worker למצב לא מקוון. בצד השרת Cloudflare Pages, Workers, D1, R2 ו-KV.

מכיוון שהאפליקציה מותקנת כ-PWA, כל פריסה מחייבת קידום גרסה בכל ההפניות ובמטמון של ה-Service Worker — אחרת מכשירים מותקנים ממשיכים להגיש קוד ישן. הגרסה החיה כרגע היא v169.

התוצאה

האפליקציה חיה ב-ma-trip.pages.dev ומותקנת כאפליקציה עצמאית ב-iPhone. מסלול, אריזה, תקציב ומפה עובדים גם במצב טיסה, והמדיה נשארת מוצפנת על המכשיר.

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