fix: retain server notifications when local alerts arrive
This commit is contained in:
@@ -1138,3 +1138,10 @@ Taslak türü seçicisine erişilebilir ad eklendi. Üretim derlemesi geçti. Ö
|
|||||||
- getUpdates doğrulaması HTTP başarısı, boolean ok:true ve gerçek sonuç dizisi gerektiriyor. Eksik/yanlış tipte sonuçlar artık boş liste gibi 'kod bulunamadı' sayılmıyor; sağlayıcı yanıt hatası olarak bildiriliyor.
|
- getUpdates doğrulaması HTTP başarısı, boolean ok:true ve gerçek sonuç dizisi gerektiriyor. Eksik/yanlış tipte sonuçlar artık boş liste gibi 'kod bulunamadı' sayılmıyor; sağlayıcı yanıt hatası olarak bildiriliyor.
|
||||||
- 269 test ve build başarılı. Yeni testler kaydedilmiş bağlantı + yapılandırılmamış hizmetin HTTP 503 vermesi, kimliksiz test kabulünün istemcide reddi/tekrarlanmaması ve bozuk güncelleme yanıtlarını kapsıyor. HTTP test sunucusunda Telegram tokenı açıkça boş bırakılıyor; yalnız geçici veri deposu kullanılıyor. Gerçek Telegram mesajı gönderilmedi.
|
- 269 test ve build başarılı. Yeni testler kaydedilmiş bağlantı + yapılandırılmamış hizmetin HTTP 503 vermesi, kimliksiz test kabulünün istemcide reddi/tekrarlanmaması ve bozuk güncelleme yanıtlarını kapsıyor. HTTP test sunucusunda Telegram tokenı açıkça boş bırakılıyor; yalnız geçici veri deposu kullanılıyor. Gerçek Telegram mesajı gönderilmedi.
|
||||||
- Test gönderimi yanıtında id alanı zorunlu hale geldi; ön yüz/sunucu birlikte güncellenmeli. Test mesajı düğmesi için kalıcı istek tekrar engeli ve gerçek sağlayıcı uçtan uca kabulü henüz doğrulanmadı. Genel plan açık.
|
- Test gönderimi yanıtında id alanı zorunlu hale geldi; ön yüz/sunucu birlikte güncellenmeli. Test mesajı düğmesi için kalıcı istek tekrar engeli ve gerçek sağlayıcı uçtan uca kabulü henüz doğrulanmadı. Genel plan açık.
|
||||||
|
|
||||||
|
### 2026-09-06 — Yerel uyarılar sunucu bildirimlerini düşürmesin
|
||||||
|
|
||||||
|
- Yeni bir yerel uyarı ekleyen `notify` işlevi tüm listeyi ilk 30 kayıtla sınırlıyordu. Bu, sunucudan yüklenen ve henüz okunmamış ihale bildirimlerini de ekrandan düşürüyordu. Yeni ortak birleştirme işlevi yalnız son 30 yerel uyarıyı tutuyor; yüklenmiş sunucu kayıtları sayı sınırına dahil edilmiyor.
|
||||||
|
- Sunucu yenilemesindeki toplam 80 kayıt kesintisi de kaldırıldı; yenileme sunucunun gönderdiği kayıtları/okunma durumunu esas alıp yerel uyarıları kendi sınırı içinde koruyor. Yerel bildirim kimlikleri aynı milisaniyede çakışabilen Date.now yerine UUID kullanıyor.
|
||||||
|
- Doğrulama: 270 test ve build başarılı. Yeni regresyon testinde 100 sunucu kaydı üzerine 40 yerel uyarı eklendi; tüm sunucu kayıtları ve okunma değerleri korundu, son 30 yerel uyarı tekil kimlikle kaldı, sunucudan yeni okunma durumu geldiğinde yerel kayıtlar korundu. Bu sayı testi istemcinin aldığı listeyi kapsar; sunucu bildirim geçmişinin bütününü/paginasyonunu kanıtlamaz.
|
||||||
|
- Bu değişiklik için gerçek tarayıcıdaki yoğun bildirim görsel kabulü henüz yapılmadı. Gerçek bildirim gönderilmedi, canlıya yayın yapılmadı. Genel plan açık.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {validateReminderPreferences,quietNow} from '../services/reminderPreferences';
|
import {validateReminderPreferences,quietNow} from '../services/reminderPreferences';
|
||||||
import {validateTeam} from '../services/teamData';
|
import {validateTeam} from '../services/teamData';
|
||||||
import {savedFilterCriteria} from '../services/savedFilterCriteria';
|
import {savedFilterCriteria} from '../services/savedFilterCriteria';
|
||||||
import {mergeServerNotifications,acknowledgeNotifications} from '../services/notifications';
|
import {mergeServerNotifications,acknowledgeNotifications,addLocalNotification} from '../services/notifications';
|
||||||
import { searchTender } from '../services/searchTender';
|
import { searchTender } from '../services/searchTender';
|
||||||
import { normalizeTenderSearch, matchingItemResults } from '../services/tenderSearch';
|
import { normalizeTenderSearch, matchingItemResults } from '../services/tenderSearch';
|
||||||
import React, {createContext, useContext, useState, useEffect, useMemo, useRef } from 'react';
|
import React, {createContext, useContext, useState, useEffect, useMemo, useRef } from 'react';
|
||||||
@@ -579,7 +579,7 @@ export const AppProvider = ({ children }) => {
|
|||||||
// Gerçek bildirim: listeye ekler; sessiz saat dışında ve push açıksa tarayıcı
|
// Gerçek bildirim: listeye ekler; sessiz saat dışında ve push açıksa tarayıcı
|
||||||
// bildirimi (Notification API) da gösterir.
|
// bildirimi (Notification API) da gösterir.
|
||||||
const notify = (title, text) => {
|
const notify = (title, text) => {
|
||||||
setNotifications((prev) => [{ id: 'n-' + Date.now(), title, text, time: 'az önce', read: false }, ...prev].slice(0, 30));
|
setNotifications(prev=>addLocalNotification(prev,title,text));
|
||||||
pushNotification(title,text);
|
pushNotification(title,text);
|
||||||
};
|
};
|
||||||
const pushNotification=(title,text)=>{
|
const pushNotification=(title,text)=>{
|
||||||
@@ -605,7 +605,7 @@ export const AppProvider = ({ children }) => {
|
|||||||
const fresh=checked.filter(row=>!row.read&&!seenServerNotifications.current.has(row.id));
|
const fresh=checked.filter(row=>!row.read&&!seenServerNotifications.current.has(row.id));
|
||||||
checked.forEach(row=>seenServerNotifications.current.add(row.id));
|
checked.forEach(row=>seenServerNotifications.current.add(row.id));
|
||||||
fresh.slice(0,5).forEach(row=>pushNotification(row.title,row.text));
|
fresh.slice(0,5).forEach(row=>pushNotification(row.title,row.text));
|
||||||
setNotifications(current=>[...checked,...current.filter(row=>!row.serverId)].slice(0,80));
|
setNotifications(current=>mergeServerNotifications(current,rows));
|
||||||
}catch(error){if(getToken()===session&&version===notificationVersion.current)setNotificationError(error.message);}
|
}catch(error){if(getToken()===session&&version===notificationVersion.current)setNotificationError(error.message);}
|
||||||
};
|
};
|
||||||
const markNotificationsRead=async()=>{
|
const markNotificationsRead=async()=>{
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
const keepRecentLocal = rows => {
|
||||||
|
let localCount=0;
|
||||||
|
return rows.filter(row=>row.serverId || ++localCount<=30);
|
||||||
|
};
|
||||||
|
export function addLocalNotification(current,title,text) {
|
||||||
|
return keepRecentLocal([{id:'n-'+crypto.randomUUID(),title,text,time:'az önce',read:false},...current]);
|
||||||
|
}
|
||||||
export function mergeServerNotifications(current,rows) {
|
export function mergeServerNotifications(current,rows) {
|
||||||
if(!Array.isArray(rows)||rows.some(row=>typeof row?.id!=='string'||typeof row.baslik!=='string'||typeof row.metin!=='string'))throw new Error('Bildirim listesi geçersiz.');
|
if(!Array.isArray(rows)||rows.some(row=>typeof row?.id!=='string'||typeof row.baslik!=='string'||typeof row.metin!=='string'))throw new Error('Bildirim listesi geçersiz.');
|
||||||
const remote=rows.map(row=>({id:'server:'+row.id,serverId:row.id,title:row.baslik,text:row.metin,
|
const remote=rows.map(row=>({id:'server:'+row.id,serverId:row.id,title:row.baslik,text:row.metin,
|
||||||
time:new Date(row.zaman).toLocaleString('tr-TR'),read:!!row.okundu,ikn:row.ikn}));
|
time:new Date(row.zaman).toLocaleString('tr-TR'),read:!!row.okundu,ikn:row.ikn}));
|
||||||
return [...remote,...current.filter(row=>!row.serverId)].slice(0,80);
|
return keepRecentLocal([...remote,...current.filter(row=>!row.serverId)]);
|
||||||
}
|
}
|
||||||
export async function acknowledgeNotifications(snapshot,request) {
|
export async function acknowledgeNotifications(snapshot,request) {
|
||||||
const unread=snapshot.filter(row=>!row.read),ids=unread.filter(row=>row.serverId).map(row=>row.serverId);
|
const unread=snapshot.filter(row=>!row.read),ids=unread.filter(row=>row.serverId).map(row=>row.serverId);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {mergeServerNotifications,acknowledgeNotifications} from '../src/services/notifications.js';
|
import {mergeServerNotifications,acknowledgeNotifications,addLocalNotification} from '../src/services/notifications.js';
|
||||||
const rows=[{id:'one',baslik:'Tarih değişti',metin:'Yeni tarih',okundu:0,zaman:'2026-09-06T10:00:00Z'}];
|
const rows=[{id:'one',baslik:'Tarih değişti',metin:'Yeni tarih',okundu:0,zaman:'2026-09-06T10:00:00Z'}];
|
||||||
test('refresh keeps stable notification identities and server unread state without acknowledging delivery',()=>{
|
test('refresh keeps stable notification identities and server unread state without acknowledging delivery',()=>{
|
||||||
const once=mergeServerNotifications([{id:'local',title:'Yerel',read:false}],rows);
|
const once=mergeServerNotifications([{id:'local',title:'Yerel',read:false}],rows);
|
||||||
@@ -17,3 +17,20 @@ test('read acknowledgement requires server success and only covers the captured
|
|||||||
const acknowledged=await acknowledgeNotifications(current,async ids=>{assert.deepEqual(ids,['one']);return {success:true,okundu:1};});
|
const acknowledged=await acknowledgeNotifications(current,async ids=>{assert.deepEqual(ids,['one']);return {success:true,okundu:1};});
|
||||||
assert.deepEqual([...acknowledged],['server:one','local']);assert.equal(acknowledged.has('arrived-later'),false);
|
assert.deepEqual([...acknowledged],['server:one','local']);assert.equal(acknowledged.has('arrived-later'),false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('local notification bursts preserve every loaded server record and its read state',()=>{
|
||||||
|
const remote=Array.from({length:100},(_,i)=>({id:String(i),baslik:'İhale '+i,metin:'Kaynak bildirimi',okundu:i%2,zaman:'2026-09-06T10:00:00Z'}));
|
||||||
|
let list=mergeServerNotifications([],remote);
|
||||||
|
const before=list.map(row=>({...row}));
|
||||||
|
for(let i=0;i<40;i++)list=addLocalNotification(list,'Yerel '+i,'İşlem sonucu');
|
||||||
|
assert.deepEqual(list.filter(row=>row.serverId),before);
|
||||||
|
const local=list.filter(row=>!row.serverId);
|
||||||
|
assert.equal(local.length,30);
|
||||||
|
assert.equal(local[0].title,'Yerel 39');assert.equal(local.at(-1).title,'Yerel 10');
|
||||||
|
assert.equal(new Set(local.map(row=>row.id)).size,30);
|
||||||
|
const refreshed=mergeServerNotifications(list,remote.map(row=>({...row,okundu:1})));
|
||||||
|
assert.equal(refreshed.filter(row=>row.serverId).length,100);
|
||||||
|
assert.ok(refreshed.filter(row=>row.serverId).every(row=>row.read));
|
||||||
|
assert.deepEqual(refreshed.filter(row=>!row.serverId),local);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user