התראות Push לדפדפן: הטמעה מלאה ואופטימיזציה

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

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

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

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
התראות Push לדפדפן: הטמעה מלאה ואופטימיזציה
בינוני
~2-3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1501
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1306
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

עם ניסיון של למעלה מ-5 שנים בהטמעת התראות push, אנו מבטיחים משלוח מאובטח ואמין. האינטגרציה הטכנית של Web Push נראית פשוטה עד שנתקלים בניואנסים של VAPID, פקיעת מינויים או חסימות דפדפן. הטמענו מנגנון זה בלמעלה מ-30 פרויקטים: מחנויות מקוונות עם מיליוני משתמשים ועד סטארטאפים עם קומץ משתמשים. ובכל פעם מצאנו מלכודות לא ברורות—לדוגמה, Safari ב-iOS לפני גרסה 16.4 פשוט מתעלם מ-push, ו-Chrome יכול למחוק מינויים בכוח ללא הודעה. במאמר זה, ננתח הטמעה אמיתית: מיצירת מפתחות VAPID ועד שליחה בצד השרת ב-Laravel וניטור משלוחים.

לפי מפרט Push API (MDN): "עובד שירות (Service Worker) חייב לטפל באירוע push כדי להציג התראה."

בעיות שאנו פותרים

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

  • פקיעת מינוי: הדפדפן מסיר אוטומטית נקודות קצה לא פעילות. ללא ניקוי קבוע, תבזבז משאבים על מינויים מתים—עד 30% ממאגר הנתונים עלול להיות לא תקף.
  • התאמה אישית: התראות כלליות ללא פילוח מניבות CTR נמוך מ-10%. פילוח לפי פעולות (עגלה נטושה, חזרה, הישג) מעלה את התגובה פי שלושה—מ-8% ל-24%.
  • שגיאות VAPID: פורמט מפתח לא נכון או subject שפג תוקפו מובילים לדחייה שקטה על ידי שירות ה-Push. זה גורם לכישלון של עד 15% מהשליחות.

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

כיצד הארכיטקטורה עובדת

השרת שלך → שירות Push (Google FCM, Mozilla Autopush) → דפדפן → עובד שירות → התראה

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

השוואת ערוצי תקשורת

ערוץ CTR מהירות משלוח התאמה אישית עלות
אימייל ~20% דקות-שעות גבוהה נמוכה
SMS ~30% שניות בינונית גבוהה
Web Push ~60% שניות גבוהה נמוכה (~$0.001 להודעה)

Web Push מנצח בשלושה פרמטרים: זול יותר מ-SMS (חיסכון של עד $0.02 להודעה), מהיר יותר מאימייל, וניתן להתאמה אישית ללא אובדן מהירות. עבור חנות איקומרס טיפוסית עם 50,000 מינויים, שליחת 10 קמפיינים בחודש מביאה לעלות push מתחת ל-$10, לעומת SMS שעלותו $500—חיסכון של 98%. עכשיו להטמעה.

כיצד פילוח מעלה את ה-CTR?

פילוח לפי אירועי מוצר נותן עלייה של פי 2-3 ב-CTR. דוגמה: חנות מקוונת שהטמיעה התראות push מותאמות אישית על סטטוס הזמנה הגדילה ביקורים חוזרים ב-30% ואת ערך ההזמנה הממוצע ב-15%. השתמשו בתגיות: לעגלה נטושה—cart-abandoned, לחדשות—news, למבצעים—promo. כך לא מעמיסים על המשתמש ומגדילים רלוונטיות. הניסיון שלנו מראה שפילוח מעלה את ה-CTR פי 2-3, מ-10% ממוצע ל-30%.

מה לעשות עם שגיאות משלוח?

שגיאות משלוח מטופלות על ידי בדיקת קודי התגובה של שירות ה-Push: 410 (מינוי הוסר), 404 (לא נמצא), 429 (הגבלת קצב). בקוד Laravel (ראה סעיף שרת), אנו מוחקים אוטומטית נקודות קצה מתות לאחר קבלת 410. זה מפחית שליחות כושלות ב-20%. אם התראות לא מגיעות, בדקו את מפתחות ה-VAPID ואת פורמט נקודת הקצה.

מדריך הטמעה שלב אחר שלב

  1. צרו מפתחות VAPID: npx web-push generate-vapid-keys, שמרו את המפתח הפרטי ב-.env.
  2. רשמו עובד שירות בצד הלקוח: בקובץ sw.js נפרד, טפלו באירועי push ו-notificationclick.
  3. רשמו את הדפדפן: השתמשו ב-PushManager.subscribe() עם המפתח הציבורי של VAPID. שלחו את אובייקט המינוי לשרת.
  4. אחסון בצד השרת: שמרו את נקודת הקצה, p256dh, וטוקן auth במאגר נתונים (לדוגמה, PostgreSQL).
  5. הגדירו את Push API: צרו את ה-payload (כותרת, גוף, אייקון, url) ושלחו באמצעות ספריית minishlink/web-push.
  6. עבדו תוצאות: הסירו נקודות קצה שהחזירו שגיאה 410 או 404.
מידע נוסף על VAPID VAPID (Voluntary Application Server Identification) הוא תקן IETF (RFC 8292). המפתח הפרטי חותם על טוקן שהדפדפן מעביר לשירות ה-Push. אם השרת לא מספק טוקן תקף, שירות ה-Push דוחה את הבקשה. ניתן ליצור מפתחות באמצעות `web-push` או ידנית באמצעות Elliptic Curve P-256.

קוד מינוי ב-TypeScript

// push-subscription.ts
const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY;

function urlBase64ToUint8Array(base64String: string): Uint8Array {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const rawData = window.atob(base64);
  return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
}

export async function subscribeToPush(): Promise<boolean> {
  if (!('PushManager' in window)) {
    console.warn('Push notifications not supported');
    return false;
  }

  const permission = await Notification.requestPermission();
  if (permission !== 'granted') return false;

  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
  });

  await fetch('/api/push/subscribe', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(subscription),
  });

  return true;
}

export async function unsubscribeFromPush(): Promise<void> {
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.getSubscription();
  if (subscription) {
    await subscription.unsubscribe();
    await fetch('/api/push/unsubscribe', {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ endpoint: subscription.endpoint }),
    });
  }
}

קוד מינוי push לדפדפן זה משתמש במפתחות VAPID וניתן לשימוש חוזר לכל פרויקט.

עובד שירות: טיפול ב-Push ולחיצות

// sw.js
self.addEventListener('push', event => {
  const data = event.data?.json() ?? {};
  const options = {
    body: data.body ?? 'Новое уведомление',
    icon: data.icon ?? '/icons/icon-192.png',
    badge: '/icons/badge-72.png',
    image: data.image,
    tag: data.tag ?? 'default',
    renotify: data.renotify ?? false,
    data: { url: data.url ?? '/' },
    actions: data.actions ?? [],
    requireInteraction: data.requireInteraction ?? false,
  };
  event.waitUntil(
    self.registration.showNotification(data.title ?? 'Уведомление', options)
  );
});

self.addEventListener('notificationclick', event => {
  event.notification.close();
  const url = event.notification.data?.url ?? '/';
  event.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true })
      .then(windowClients => {
        const existing = windowClients.find(c => c.url === url && 'focus' in c);
        if (existing) return existing.focus();
        return clients.openWindow(url);
      })
  );
});

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

שרת ב-Laravel: מינוי, שליחה וניקוי

// Миграция таблицы push_subscriptions
Schema::create('push_subscriptions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
    $table->string('endpoint')->unique();
    $table->string('public_key');
    $table->string('auth_token');
    $table->json('user_agent_data')->nullable();
    $table->timestamps();
});

// Контроллер подписки
class PushSubscriptionController extends Controller {
    public function subscribe(Request $request): JsonResponse {
        $data = $request->validate([
            'endpoint' => 'required|url',
            'keys.p256dh' => 'required|string',
            'keys.auth' => 'required|string',
        ]);

        PushSubscription::updateOrCreate(
            ['endpoint' => $data['endpoint']],
            [
                'user_id' => auth()->id(),
                'public_key' => $data['keys']['p256dh'],
                'auth_token' => $data['keys']['auth'],
            ]
        );

        return response()->json(['status' => 'ok']);
    }
}

// Класс отправки уведомлений
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

class SendPushNotification {
    public function send(PushSubscription $sub, array $payload): void {
        $webPush = new WebPush([
            'VAPID' => [
                'subject' => config('services.vapid.subject'),
                'publicKey' => config('services.vapid.public_key'),
                'privateKey' => config('services.vapid.private_key'),
            ],
        ]);

        $webPush->queueNotification(
            Subscription::create([
                'endpoint' => $sub->endpoint,
                'contentEncoding' => 'aesgcm',
                'keys' => [
                    'p256dh' => $sub->public_key,
                    'auth' => $sub->auth_token,
                ],
            ]),
            json_encode($payload)
        );

        foreach ($webPush->flush() as $report) {
            if (!$report->isSuccess()) {
                if ($report->isSubscriptionExpired()) {
                    PushSubscription::where('endpoint', $report->getEndpoint())->delete();
                }
            }
        }
    }
}

מערכת התראות ה-push שלנו ב-Laravel משתלבת בצורה חלקה עם ספריית Minishlink WebPush להגדרת Push API.

תרחיש: התראות סטטוס הזמנה

המשתמש מבצע הזמנה—המערכת שולחת אוטומטית push: "הזמנה #123: הסטטוס שונה ל'נשלח'". זה מיושם באמצעות אירוע // push-subscription.ts const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY; function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))); } export async function subscribeToPush(): Promise<boolean> { if (!('PushManager' in window)) { console.warn('Push notifications not supported'); return false; } const permission = await Notification.requestPermission(); if (permission !== 'granted') return false; const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY), }); await fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription), }); return true; } export async function unsubscribeFromPush(): Promise<void> { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); await fetch('/api/push/unsubscribe', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ endpoint: subscription.endpoint }), }); } } . לשם כך, אנו יוצרים seeder או listener שכאשר סטטוס ההזמנה משתנה, אוסף את המינויים של המשתמש ושולח דרך // sw.js self.addEventListener('push', event => { const data = event.data?.json() ?? {}; const options = { body: data.body ?? 'Новое уведомление', icon: data.icon ?? '/icons/icon-192.png', badge: '/icons/badge-72.png', image: data.image, tag: data.tag ?? 'default', renotify: data.renotify ?? false, data: { url: data.url ?? '/' }, actions: data.actions ?? [], requireInteraction: data.requireInteraction ?? false, }; event.waitUntil( self.registration.showNotification(data.title ?? 'Уведомление', options) ); }); self.addEventListener('notificationclick', event => { event.notification.close(); const url = event.notification.data?.url ?? '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }) .then(windowClients => { const existing = windowClients.find(c => c.url === url && 'focus' in c); if (existing) return existing.focus(); return clients.openWindow(url); }) ); }); .

class OrderStatusChanged {
    public function handle(Order $order): void {
        $user = $order->user;
        $subscriptions = PushSubscription::where('user_id', $user->id)->get();
        foreach ($subscriptions as $sub) {
            $this->sender->send($sub, [
                'title' => 'Статус заказа изменён',
                'body' => "Заказ #{$order->number}: {$order->status_label}",
                'icon' => '/icons/order-icon.png',
                'url' => "/account/orders/{$order->id}",
                'tag' => "order-{$order->id}",
                'renotify' => true,
                'actions' => [
                    ['action' => 'view', 'title' => 'Посмотреть заказ'],
                    ['action' => 'dismiss', 'title' => 'Закрыть'],
                ],
            ]);
        }
    }
}

מה אתם מקבלים

  • קוד עובד שירות מלא וקוד מינוי בצד הלקוח ב-TypeScript.
  • קוד בצד השרת ב-Laravel עם migrations, controller ומחלקת שליחה.
  • אנליטיקת משלוח מוגדרת וניקוי אוטומטי של מינויים מתים.
  • תיעוד: תיאור API, הוראות למנהל, גישה ללוגים.
  • תמיכה למשך שבועיים לאחר ההטמעה.

שלבים ולוחות זמנים

שלב משך תוצאה
ביקורת ארכיטקטורה נוכחית יום אחד תוכנית אינטגרציה בהתחשב בסטACK שלך
הגדרת VAPID ועובד שירות 0.5 יום עובד שירות מוכן עם טיפול ב-push
הטמעת מינוי (front+back) 1–2 ימים מינוי/ביטול מינוי עובד
הגדרת תרחישי שליחה יום אחד התראות מבוססות אירועים (הזמנה, רישום)
בדיקות וניטור 0.5 יום דוח משלוח + לוגי שגיאות

לוח זמנים: 1 עד 3 ימים תלוי במורכבות התרחיש.

Web push למעורבות אתר הוא ערוץ מוכח; התראות push מוגדרות כראוי מגדילות ביקורים חוזרים ב-30% ואת ערך ההזמנה הממוצע ב-15%. לעיון נוסף: MDN Web Push API — תיעוד רשמי. התראות push בדפדפן נתמכות ב-Chrome, Firefox, Edge, Opera ו-Safari (מגרסה 16.4). שליחת התראות push דרך VAPID מבטיחה אימות ואבטחה.

מדריך זה מכסה את תהליך העבודה המלא של הטמעת התראות push, כולל טיפול בעובד שירות, הגדרת התראות push ב-Laravel והגדרת Push API.