PageSourceSearch

https://vidgap.com/firebase-messaging-sw.js

js vidgap.com collected 2026-10-08 23:42:40 UTC 14,796 bytes, 339 lines download raw bytes

1// 1. Gunakan versi spesifik 9.23.0 agar link valid
2importScripts('https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js');
3importScripts('https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-compat.js');
4
5// 2. Inisialisasi dengan Config yang benar (Copy-paste dari Firebase Console)
6const firebaseConfig = {
7    apiKey: "AIzaSyDOqj1WZ3aa22YxD9pj-Fm2UgNy66kioK4",
8    authDomain: "vidgap-push-notification.firebaseapp.com",
9    projectId: "vidgap-push-notification",
10    storageBucket: "vidgap-push-notification.firebasestorage.app",
11    messagingSenderId: "192165573723",
12    appId: "1:192165573723:web:9a8e80111efff4c42f7b7e"
13};
14
15firebase.initializeApp(firebaseConfig);
16
17// 3. Ambil objek messaging
18const messaging = firebase.messaging();
19
20const META_DB = "FcmAnalytics";
21const META_STORE = "meta";
22
23function openMetaDb() {
24    return new Promise((resolve, reject) => {
25        const request = indexedDB.open(META_DB, 1);
26        request.onupgradeneeded = (event) => {
27            const db = event.target.result;
28            if (!db.objectStoreNames.contains(META_STORE)) {
29                db.createObjectStore(META_STORE);
30            }
31        };
32        request.onsuccess = () => resolve(request.result);
33        request.onerror = () => reject(request.error);
34    });
35}
36
37function saveInstallationId(installationId) {
38    if (!installationId) {
39        return Promise.resolve();
40    }
41    return openMetaDb()
42        .then((db) => new Promise((resolve) => {
43            const tx = db.transaction(META_STORE, "readwrite");
44            tx.objectStore(META_STORE).put(installationId, "installationId");
45            tx.oncomplete = () => resolve();
46            tx.onerror = () => resolve();
47        }))
48        .catch(() => undefined);
49}
50
51function getStoredInstallationId() {
52    return openMetaDb()
53        .then((db) => new Promise((resolve) => {
54            const tx = db.transaction(META_STORE, "readonly");
55            const request = tx.objectStore(META_STORE).get("installationId");
56            request.onsuccess = () => resolve(request.result || "");
57            request.onerror = () => resolve("");
58        }))
59        .catch(() => "");
60}
61
62function resolveAllowedUrl(url) {
63    if (!url || typeof url !== "string") {
64        return self.location.origin + "/";
65    }
66    try {
67        const parsed = new URL(url, self.location.origin);
68        const host = parsed.hostname;
69        const sameOrigin = parsed.origin === self.location.origin;
70        const vidgapHost = host === "vidgap.com" || host.endsWith(".vidgap.com");
71        if (sameOrigin || vidgapHost) {
72            return parsed.href;
73        }
74    } catch (error) {
75        // invalid URL
76    }
77    return self.location.origin + "/";
78}
79
80function reportDelivered(payloadData, title, body) {
81    const notificationId = payloadData.notification_id || payloadData.article_id || "";
82    if (!notificationId) {
83        return Promise.resolve();
84    }
85
86    return getStoredInstallationId()
87        .then((installationId) =>
88            fetch("/api/notifications/delivered", {
89                method: "POST",
90                headers: { "Content-Type": "application/json" },
91                body: JSON.stringify({
92                    notificationId,
93                    article_id: payloadData.article_id || notificationId,
94                    campaignId: payloadData.campaign_id || "",
95                    type: payloadData.type || "",
96                    url: payloadData.url || "",
97                    title,
98                    body,
99                    installationId,
100                }),
101            })
102        )
103        .catch(() => undefined);
104}
105
106self.addEventListener("message", (event) => {
107    if (event.data && event.data.type === "FCM_INSTALLATION_ID") {
108        saveInstallationId(event.data.installationId);
109    }
110});
111
112self.addEventListener("notificationclick", (event) => {
113    event.notification.close();
114    const data = event.notification.data || {};
115    const notificationId = data.notificationId || data.article_id || "";
116    const targetUrl = resolveAllowedUrl(data.url);
117
118    event.waitUntil((async () => {
119        try {
120            const installationId = await getStoredInstallationId();
121            if (notificationId && installationId) {
122                await fetch("/api/notifications/opened", {
123                    method: "POST",
124                    headers: { "Content-Type": "application/json" },
125                    body: JSON.stringify({
126                        notificationId,
127                        installationId,
128                        campaignId: data.campaignId || "",
129                        type: data.type || "",
130                    }),
131                });
132            }
133        } catch (error) {
134            // open tracking must not block navigation
135        }
136
137        const windowClients = await self.clients.matchAll({
138            type: "window",
139            includeUncontrolled: true,
140        });
141
142        for (const client of windowClients) {
143            if ("focus" in client) {
144                await client.focus();
145                if (targetUrl && "navigate" in client) {
146                    try {
147                        await client.navigate(targetUrl);
148                    } catch (error) {
149                        // keep focused tab if navigate is unsupported
150                    }
151                }
152                return;
153            }
154        }
155
156        if (self.clients.openWindow) {
157            await self.clients.openWindow(targetUrl);
158        }
159    })());
160});
161
162// 4. Pastikan messaging ada sebelum panggil onBackgroundMessage
163if (messaging) {
164    messaging.onBackgroundMessage((payload) => {
165        // Note: Service Worker tidak bisa akses process.env, jadi log selalu muncul
166        // Tapi ini penting untuk debugging notifikasi, jadi biarkan tetap ada
167        console.log('[sw.js] Pesan masuk di background:', payload);
168
169        const payloadData = payload.data || {};
170        const notificationTitle = (payload.notification && payload.notification.title) || "Pesan Baru";
171        const title = payload.notification && payload.notification.title;
172        const body = payload.notification && payload.notification.body;
173        const notificationId = payloadData.notification_id || payloadData.article_id || "";
174        const notificationOptions = {
175            body: body || "Ada info baru untukmu.",
176            icon: '/next.svg', // Pastikan file ini ada di folder public/
177            data: {
178                notificationId,
179                campaignId: payloadData.campaign_id || "",
180                type: payloadData.type || "",
181                url: payloadData.url || "/",
182                article_id: payloadData.article_id || notificationId,
183            },
184        };
185
186        // Cek apakah notifikasi perlu disimpan ke database
187        const { is_save_db, article_id, country_code } = payloadData;
188
189        if (is_save_db === "true" && article_id) {
190            // Jalankan operasi async untuk simpan ke IndexedDB dan server
191            const syncTask = async () => {
192                try {
193                    // 1. Open IndexedDB
194                    const dbRequest = indexedDB.open("NotificationStore", 1);
195                    
196                    dbRequest.onupgradeneeded = (event) => {
197                        const db = event.target.result;
198                        if (!db.objectStoreNames.contains("notifications")) {
199                            const store = db.createObjectStore("notifications", { keyPath: "article_id" });
200                            // Create index for efficient querying
201                            store.createIndex("sync_status", "sync_status", { unique: false });
202                            store.createIndex("at", "at", { unique: false });
203                        }
204                    };
205
206                    dbRequest.onsuccess = async (event) => {
207                        const db = event.target.result;
208                        
209                        // 2. Check if notification already exists (prevent duplicates)
210                        const txCheck = db.transaction("notifications", "readonly");
211                        const storeCheck = txCheck.objectStore("notifications");
212                        const existing = await new Promise((resolve) => {
213                            const req = storeCheck.get(article_id);
214                            req.onsuccess = () => resolve(req.result);
215                            req.onerror = () => resolve(null);
216                        });
217
218                        // If already exists and synced, skip
219                        if (existing && existing.sync_status === 'synced') {
220                            console.log('[sw.js] Notification already exists and synced:', article_id);
221                            return;
222                        }
223
224                        // 3. Save to IndexedDB with pending status
225                        const tx = db.transaction("notifications", "readwrite");
226                        const store = tx.objectStore("notifications");
227
228                        const notificationData = {
229                            article_id,
230                            title,
231                            body,
232                            country_code: country_code || '',
233                            sync_status: 'pending',
234                            at: new Date().toISOString(),
235                            retry_count: existing ? (existing.retry_count || 0) : 0,
236                        };
237
238                        store.put(notificationData);
239
240                        console.log('[sw.js] Data disimpan ke IndexedDB:', article_id);
241
242                        // 4. Try to send to server (with retry mechanism)
243                        const maxRetries = 3;
244                        let retryCount = 0;
245                        let saveSuccess = false;
246
247                        while (retryCount < maxRetries && !saveSuccess) {
248                            try {
249                                const res = await fetch('/api/notifications/save', {
250                                    method: 'POST',
251                                    body: JSON.stringify({ 
252                                        article_id, 
253                                        title, 
254                                        body,
255                                        country_code,
256                                        notificationId,
257                                        campaignId: payloadData.campaign_id || "",
258                                        type: payloadData.type || "",
259                                        url: payloadData.url || "",
260                                        metadata: {
261                                            source: 'service_worker',
262                                            received_at: new Date().toISOString(),
263                                        }
264                                    }),
265                                    headers: { 'Content-Type': 'application/json' }
266                                });
267                                
268                                if (res.ok) {
269                                    const result = await res.json();
270                                    console.log('[sw.js] Data berhasil dikirim ke server:', article_id);
271                                    
272                                    // Update status to synced
273                                    const tx2 = db.transaction("notifications", "readwrite");
274                                    const store2 = tx2.objectStore("notifications");
275                                    store2.put({ 
276                                        article_id, 
277                                        title, 
278                                        body,
279                                        country_code: country_code || '',
280                                        sync_status: 'synced',
281                                        at: notificationData.at,
282                                        synced_at: new Date().toISOString(),
283                                        is_duplicate: result.isDuplicate || false,
284                                        retry_count: retryCount,
285                                    });
286                                    
287                                    saveSuccess = true;
288                                } else {
289                                    const errorText = await res.text();
290                                    console.log(`[sw.js] Gagal sync (attempt ${retryCount + 1}/${maxRetries}):`, errorText);
291                                    retryCount++;
292                                    
293                                    // Wait before retry (exponential backoff)
294                                    if (retryCount < maxRetries) {
295                                        await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
296                                    }
297                                }
298                            } catch (e) { 
299                                console.log(`[sw.js] Network error (attempt ${retryCount + 1}/${maxRetries}):`, e.message);
300                                retryCount++;
301                                
302                                // Wait before retry
303                                if (retryCount < maxRetries) {
304                                    await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
305                                }
306                            }
307                        }
308
309                        // If all retries failed, update retry count in IndexedDB
310                        if (!saveSuccess) {
311                            console.log('[sw.js] All sync attempts failed, marked as pending for later sync');
312                            const tx3 = db.transaction("notifications", "readwrite");
313                            const store3 = tx3.objectStore("notifications");
314                            store3.put({
315                                ...notificationData,
316                                retry_count: retryCount,
317                                last_retry_at: new Date().toISOString(),
318                            });
319                        }
320                    };
321
322                    dbRequest.onerror = (event) => {
323                        console.error('[sw.js] Error membuka IndexedDB:', event.target.error);
324                    };
325                } catch (error) {
326                    console.error('[sw.js] Error dalam syncTask:', error);
327                }
328            };
329            
330            // Jalankan sync task
331            syncTask();
332        }
333
334        return Promise.all([
335            self.registration.showNotification(notificationTitle, notificationOptions),
336            reportDelivered(payloadData, title, body),
337        ]);
338    });
339}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.