ReleaseNotes düzeltmesi
This commit is contained in:
parent
ee121db521
commit
cced417ede
10 changed files with 194 additions and 68 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -2,4 +2,8 @@ configs/**/data/**
|
|||
*.bak
|
||||
**/node_modules
|
||||
**/.DS_Store
|
||||
logs/
|
||||
logs/
|
||||
|
||||
# Build sırasında git tag'lerinden üretilir (api/scripts/generate-release-notes.sh).
|
||||
# Takip edilirse deploy'daki `git pull` yerel değişiklikle çakışır ve dosya eskir.
|
||||
api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
|
||||
|
|
@ -2,26 +2,29 @@
|
|||
set -e
|
||||
|
||||
# Sürüm notlarını git tag'lerinden üretir:
|
||||
# api/release-notes/release-notes.json
|
||||
# api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
|
||||
#
|
||||
# Bu dosya API konteynerine /etc/api/release-notes altına MOUNT edilir (imaja
|
||||
# GÖMÜLMEZ). Uygulama açılışında ReleaseNoteStartupService onu `Sas_H_ReleaseNote`
|
||||
# tablosuyla eşitler. Yani yeni bir sürüm tag'i için ne DbMigrator ne de imaj
|
||||
# derlemesi gerekir; deploy sırasında bu script koşar ve konteyner yeniden
|
||||
# başlar. İstemci sürüm numarasını ve changelog'u application-configuration
|
||||
# yanıtındaki `extraProperties.changeLogs` alanından okur.
|
||||
# Dosya csproj'daki `Content Update="release-notes.json"` kuralıyla publish
|
||||
# çıktısına kopyalanır, yani imaja gömülür ve konteynerde ContentRootPath
|
||||
# altında (/srv/app) yer alır. ReleaseNoteStartupService açılışta tam bu yolu
|
||||
# okuyup `Sas_H_ReleaseNote` tablosuyla eşitler; DbMigrator gerekmez.
|
||||
#
|
||||
# Kaynak ağacının DIŞINDA bir klasöre yazılmasının sebebi Docker katman cache'i:
|
||||
# dosya `src/` altında olsaydı Dockerfile'daki `COPY . .` her tag'de geçersizleşip
|
||||
# tüm `dotnet publish` adımını yeniden çalıştırırdı.
|
||||
# ÖNEMLİ: Çıktı yolu ReleaseNoteStartupService'in okuduğu yolla aynı olmak
|
||||
# ZORUNDA. Daha önce script `api/release-notes/` altına yazıyordu; orası ne
|
||||
# publish çıktısına kopyalanıyor ne de konteynere mount ediliyordu, dolayısıyla
|
||||
# imajdaki eski (commit'lenmiş) dosya kullanılıyor ve yeni tag'ler hiç
|
||||
# görünmüyordu.
|
||||
#
|
||||
# Deploy akışında deploy/app.sh tarafından otomatik çağrılır; elle de
|
||||
# çalıştırılabilir. Çıktı git'e commit EDİLMEZ (.gitignore).
|
||||
# Çıktı .gitignore'dadır: takip edilseydi deploy'daki `git pull` her seferinde
|
||||
# yerel değişiklikle çakışırdı.
|
||||
#
|
||||
# Deploy akışında configs/deployment/scripts/build/api.sh tarafından otomatik
|
||||
# çağrılır; elle de çalıştırılabilir.
|
||||
#
|
||||
# Dış bağımlılığı yoktur (jq/node gerekmez): JSON kaçışları sed ile yapılır.
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
OUTPUT="release-notes/release-notes.json"
|
||||
OUTPUT="src/Sozsoft.Platform.HttpApi.Host/release-notes.json"
|
||||
mkdir -p "$(dirname "$OUTPUT")"
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
|
|
|
|||
|
|
@ -108,8 +108,12 @@ public class ReleaseNoteStartupService : IHostedService
|
|||
using var scope = _serviceProvider.CreateScope();
|
||||
var (inserted, updated, deleted) = await SyncItemsAsync(scope.ServiceProvider, items);
|
||||
|
||||
// En son sürüm de loglanır: dosya eskiyse (üretim adımı atlanmış
|
||||
// veya yanlış yola yazılmışsa) "0 eklendi" tek başına yanıltıcı olur.
|
||||
_logger.LogInformation(
|
||||
"Sürüm notları senkronize edildi: {Total} tag, {Inserted} eklendi, {Updated} güncellendi, {Deleted} silindi.",
|
||||
"Sürüm notları senkronize edildi ({Path}, en son sürüm {Latest}): {Total} tag, {Inserted} eklendi, {Updated} güncellendi, {Deleted} silindi.",
|
||||
path,
|
||||
items[0].Version,
|
||||
items.Count,
|
||||
inserted,
|
||||
updated,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
{
|
||||
"ReleaseNotes": [
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"releaseDate": "2026-08-13",
|
||||
"commit": "798acbef9e6a26e29e58f10b13cb8e63ac5fa6b4",
|
||||
"commit": "ee121db5211ffbd28496b87ce9e4776ff96f7640",
|
||||
"notes": [
|
||||
"Claude.ai optimizasyon güncellemesi",
|
||||
"Zaafiyet testleri ve güncellemeleri",
|
||||
"React 19 upgrade",
|
||||
"Devexpress 26.1.4",
|
||||
"Migrations ve Seeds klasör düzenlemesi",
|
||||
"New Version Check komponent düzenlemesi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.1.11",
|
||||
"releaseDate": "2026-08-11",
|
||||
"commit": "783c1fe1d35f7cb8f23fb63fad26123cbe54aafc",
|
||||
"notes": [
|
||||
"Claude.ai optimizasyon güncellemesi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.1.10",
|
||||
"releaseDate": "2026-07-31",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
#!/bin/bash
|
||||
# set -e: sürüm notu üretimi sessizce başarısız olursa imaj eski/eksik notlarla
|
||||
# derlenmesin. Daha önce böyle olmuştu: dosya git'te takip edildiği için hata
|
||||
# fark edilmiyordu.
|
||||
set -e
|
||||
|
||||
cd ~/sozsoft-platform
|
||||
git checkout main
|
||||
# --tags --force: sürüm notları git tag'lerinden üretiliyor; yerel tag origin'den
|
||||
|
|
@ -8,7 +14,19 @@ cd ~/sozsoft-platform/api
|
|||
|
||||
# Sürüm notları git tag'lerinden üretilir ve API imajına gömülür; uygulama
|
||||
# açılışta bunları veritabanına senkronize eder (DbMigrator gerekmez).
|
||||
./scripts/generate-release-notes.sh
|
||||
# `bash ...` ile çağrılır: depo Windows'tan yönetildiği için çalıştırma bitinin
|
||||
# kaybolması "Permission denied" verip sürüm notlarını sessizce düşürüyordu.
|
||||
RELEASE_NOTES=src/Sozsoft.Platform.HttpApi.Host/release-notes.json
|
||||
bash scripts/generate-release-notes.sh
|
||||
|
||||
# Dosya publish çıktısına csproj üzerinden girer; yoksa imaj sürüm notsuz kalır
|
||||
# ve API açılışta "release-notes.json bulunamadı" uyarısı verir. Sessizce devam
|
||||
# etmek yerine burada dur.
|
||||
if [ ! -s "$RELEASE_NOTES" ]; then
|
||||
echo "HATA: ${RELEASE_NOTES} üretilemedi, imaj derlenmiyor." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$(cat ~/sozsoft-platform/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json \
|
||||
| grep -o '"[^"]*"\s*:\s*"[^"]*"' \
|
||||
| grep -E '^"(Version)"' \
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Link } from 'react-router-dom'
|
|||
import { Avatar } from '../ui'
|
||||
import {
|
||||
FcEngineering,
|
||||
FcNews,
|
||||
FcNext,
|
||||
FcNfcSign,
|
||||
FcSynchronize,
|
||||
|
|
@ -142,6 +143,18 @@ const UserDropdownBase = ({ className }: CommonProps) => {
|
|||
</span>
|
||||
</Dropdown.Item>
|
||||
|
||||
<Dropdown.Item eventKey="ChangeLog" className="mb-1 px-0">
|
||||
<Link
|
||||
className="flex h-full w-full items-center gap-2 px-2"
|
||||
to={ROUTES_ENUM.protected.admin.changeLog}
|
||||
>
|
||||
<span className="text-xl opacity-50">
|
||||
<FcNews />
|
||||
</span>
|
||||
<span>{translate('::App.ChangeLog')}</span>
|
||||
</Link>
|
||||
</Dropdown.Item>
|
||||
|
||||
{/* <Dropdown.Item variant="divider" /> */}
|
||||
<Dropdown.Item eventKey="Sign Out" className="mb-1 px-2" onClick={signOut}>
|
||||
<span className="text-xl opacity-50">
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ export interface LocaleStoreModel {
|
|||
currentLang: string
|
||||
currentUiVersion: string | undefined
|
||||
currentTenantName?: string
|
||||
/** Dolu ise kullanıcı bir sonraki girişinde ChangeLog ekranına yönlendirilir. */
|
||||
pendingChangelogVersion?: string
|
||||
}
|
||||
|
||||
export interface LocaleStoreActions {
|
||||
|
|
@ -14,6 +16,7 @@ export interface LocaleStoreActions {
|
|||
onSetLang: ThunkOn<LocaleModel, Injections, StoreModel>
|
||||
setUiVersion: Action<LocaleStoreModel, string | undefined>
|
||||
setTenantName: Action<LocaleStoreModel, string | undefined>
|
||||
clearPendingChangelog: Action<LocaleStoreModel, void>
|
||||
}
|
||||
|
||||
export type LocaleModel = LocaleStoreModel & LocaleStoreActions
|
||||
|
|
@ -22,6 +25,7 @@ const initialState: LocaleStoreModel = {
|
|||
currentLang: appConfig.locale,
|
||||
currentUiVersion: appConfig.uiVersion,
|
||||
currentTenantName: undefined,
|
||||
pendingChangelogVersion: undefined,
|
||||
}
|
||||
|
||||
export const localeModel: LocaleModel = {
|
||||
|
|
@ -36,10 +40,41 @@ export const localeModel: LocaleModel = {
|
|||
helpers.getStoreActions().abpConfig.getConfig(false)
|
||||
},
|
||||
),
|
||||
/**
|
||||
* Backend'den gelen çalışan sürümü işler (application-configuration →
|
||||
* `extraProperties.changeLogs.uiVersion`).
|
||||
*
|
||||
* Sürüm son görülenden farklıysa changelog bayrağı yazılır; kullanıcı bir
|
||||
* sonraki girişinde ChangeLog ekranına yönlendirilir. Tetikleyici bilerek
|
||||
* bundle değil sürümdür: yalnızca yeni bir git tag'i eklendiğinde de kullanıcı
|
||||
* değişiklikleri görür.
|
||||
*
|
||||
* Referans ayrı bir alan değil `currentUiVersion`'ın kendisidir; zaten persist
|
||||
* edildiği için "son görülen sürüm" bilgisini o taşır. Ayrı bir alan eklemek
|
||||
* mevcut kullanıcılarda bir sürümlük boşluk yaratırdı (yeni alan boş olduğu
|
||||
* için ilk deploy sessizce atlanır).
|
||||
*
|
||||
* Yeni tarayıcıda `currentUiVersion` boştur (`appConfig.uiVersion = undefined`);
|
||||
* kullanıcı ilk girişinde changelog'a düşmesin diye bayrak yazılmaz.
|
||||
*
|
||||
* Durum bu model üzerinden persist edilir (`[EasyPeasyStore][0].locale`);
|
||||
* ayrı `localStorage` anahtarı KULLANILMAZ.
|
||||
*/
|
||||
setUiVersion: action((state, payload) => {
|
||||
// Config henüz yüklenmediğinde çağrı boş gelir; persist edilmiş "son görülen
|
||||
// sürüm" bu yüzden ezilmemeli, yoksa her açılışta referans sıfırlanır ve
|
||||
// bayrak bir daha hiç yazılmaz.
|
||||
if (!payload) return
|
||||
|
||||
const lastSeen = state.currentUiVersion
|
||||
state.currentUiVersion = payload
|
||||
|
||||
if (lastSeen && lastSeen !== payload) state.pendingChangelogVersion = payload
|
||||
}),
|
||||
setTenantName: action((state, payload) => {
|
||||
state.currentTenantName = payload
|
||||
}),
|
||||
clearPendingChangelog: action((state) => {
|
||||
state.pendingChangelogVersion = undefined
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { jwtDecode } from 'jwt-decode'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import useQuery from './useQuery'
|
||||
import { useStoreActions, useStoreState } from '../../store/store'
|
||||
import { store, useStoreActions, useStoreState } from '../../store/store'
|
||||
import {
|
||||
FailedSignInResponse,
|
||||
SignInCredential,
|
||||
|
|
@ -13,7 +13,6 @@ import { isLoginSuccess } from '../../proxy/account/models'
|
|||
import { REDIRECT_URL_KEY } from '../../constants/app.constant'
|
||||
import appConfig from '../../proxy/configs/app.config'
|
||||
import { register } from '../../services/account.service'
|
||||
import { hasPendingChangelog } from '@/views/version/swRegistration'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
|
||||
type Status = 'success' | 'failed' | 'error'
|
||||
|
|
@ -72,7 +71,11 @@ function useAuth() {
|
|||
const redirectUrl = query.get(REDIRECT_URL_KEY)
|
||||
// Uygulama yeni bir sürümle/deploy ile açıldıysa kullanıcı önce
|
||||
// değişiklikleri görsün. Bayrak ChangeLog ekranında tüketilir.
|
||||
const target = hasPendingChangelog()
|
||||
//
|
||||
// Değer hook state'inden değil doğrudan store'dan okunur: bayrak giriş
|
||||
// sayfasındaki config yüklemesiyle yazılıyor, render kapanışı bayat
|
||||
// kalabilir.
|
||||
const target = store.getState().locale.pendingChangelogVersion
|
||||
? ROUTES_ENUM.protected.admin.changeLog
|
||||
: (redirectUrl ?? appConfig.authenticatedEntryPath)
|
||||
navigate(target)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import React, { useEffect, useState } from 'react'
|
|||
import PageTitle from '@/components/shared/PageTitle'
|
||||
import { FaRegCheckCircle, FaSyncAlt, FaTag } from 'react-icons/fa'
|
||||
import { releaseNoteService, type ReleaseNoteDto } from '@/services/releaseNote.service'
|
||||
import { checkForAppUpdate, clearPendingChangelog } from './swRegistration'
|
||||
import { checkForAppUpdate } from './swRegistration'
|
||||
import { useStoreActions } from '@/store'
|
||||
|
||||
// User dropdown reuses the exact update-check flow owned by this component.
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
|
|
@ -106,6 +107,7 @@ const Log = ({
|
|||
const Changelog = () => {
|
||||
const { translate } = useLocalization()
|
||||
const { isCheckingUpdate, checkForUpdate } = useManualUpdateCheck()
|
||||
const clearPendingChangelog = useStoreActions((actions) => actions.locale.clearPendingChangelog)
|
||||
|
||||
// Kaynak `ReleaseNote` entity'sidir; liste yalnızca bu ekran açıldığında
|
||||
// istenir (application-configuration sadece çalışan sürümü taşır).
|
||||
|
|
@ -120,7 +122,7 @@ const Changelog = () => {
|
|||
.getList()
|
||||
.then(setReleases)
|
||||
.catch((error: unknown) => console.warn('Sürüm notları alınamadı.', error))
|
||||
}, [])
|
||||
}, [clearPendingChangelog])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ import './installPrompt'
|
|||
* 2. Yeni worker bulunursa kurulum başlar; `src/sw.ts` indirdiği her dosyayı
|
||||
* isim/boyut/yüzde olarak yayınlar, bu modül state'e çevirir.
|
||||
* 3. Kurulum bitince SKIP_WAITING → `controllerchange` → reload.
|
||||
* 4. Backend'den gelen sürüm son görülenden farklıysa changelog bayrağı
|
||||
* yazılır; login sonrası kullanıcı ChangeLog ekranına yönlendirilir.
|
||||
* Tetikleyici sürümdür, bundle değil: yalnızca yeni bir sürüm notu (git
|
||||
* tag) eklendiğinde de kullanıcı değişiklikleri görür.
|
||||
* 4. Changelog yönlendirmesi bu modülün işi DEĞİLDİR: son görülen sürüm ve
|
||||
* bekleyen changelog bayrağı store'un `locale` diliminde tutulur
|
||||
* (`setUiVersion` / `pendingChangelogVersion`), böylece durum tek yerde ve
|
||||
* `[EasyPeasyStore][0]` içinde persist edilir. Bu modül yalnızca güncelleme
|
||||
* penceresinin gösterdiği sürüm/not bilgisini tutar.
|
||||
*
|
||||
* Bu dosya entry chunk'ındadır: React/UI/store bağımlılığı yoktur, pencere
|
||||
* yalnızca gerçekten indirme varken lazy yüklenir.
|
||||
|
|
@ -111,14 +112,22 @@ const VERSION_POLL_INTERVAL = 30 * 60 * 1000
|
|||
const MIN_CHECK_GAP = 30 * 1000
|
||||
const ACTIVATION_RETRY_DELAY = 8_000
|
||||
const ACTIVATION_TIMEOUT = 45_000
|
||||
/** İndirme bittikten sonra geçişin kendiliğinden başlaması için tanınan süre. */
|
||||
const ACTIVATION_HANDOFF_TIMEOUT = 15_000
|
||||
const REGISTRATION_TIMEOUT = 10_000
|
||||
const UPDATE_CALL_TIMEOUT = 20_000
|
||||
/** Kurulum/aktivasyon başarısız olduğunda otomatik yeniden deneme süresi. */
|
||||
const RETRY_DELAY_SECONDS = 15
|
||||
|
||||
const LAST_VERSION_KEY = 'sozsoft:last-version'
|
||||
const PENDING_CHANGELOG_KEY = 'sozsoft:pending-changelog'
|
||||
const CHUNK_RELOAD_GUARD_KEY = 'sozsoft:chunk-reload'
|
||||
/**
|
||||
* Chunk yeniden yükleme koruması: sonsuz reload döngüsünü keser.
|
||||
*
|
||||
* Store'da tutulamaz — reload'ın hemen öncesinde, React/store mount edilmeden
|
||||
* yazılması gerekiyor. Bu yüzden modüldeki tek doğrudan depo anahtarı budur;
|
||||
* sürüm/changelog durumu store'un `locale` diliminde durur. Bayrak 10 saniye
|
||||
* sonra silindiği için kalıcı bir kayıt bırakmaz.
|
||||
*/
|
||||
const CHUNK_RELOAD_GUARD_KEY = 'chunkReloadGuard'
|
||||
|
||||
/** Service worker yalnızca prod'da, dev'de ise VITE_PWA_DEV=true ile aktiftir. */
|
||||
const isServiceWorkerEnabled = import.meta.env.PROD || import.meta.env.VITE_PWA_DEV === 'true'
|
||||
|
|
@ -129,19 +138,18 @@ const withTimeout = <T>(promise: Promise<T>, ms: number) =>
|
|||
new Promise<undefined>((resolve) => window.setTimeout(() => resolve(undefined), ms)),
|
||||
])
|
||||
|
||||
const readStorage = (key: string, session = false) => {
|
||||
const readStorage = (key: string) => {
|
||||
try {
|
||||
return (session ? sessionStorage : localStorage).getItem(key) ?? undefined
|
||||
return localStorage.getItem(key) ?? undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const writeStorage = (key: string, value: string | undefined, session = false) => {
|
||||
const writeStorage = (key: string, value: string | undefined) => {
|
||||
try {
|
||||
const store = session ? sessionStorage : localStorage
|
||||
if (value === undefined) store.removeItem(key)
|
||||
else store.setItem(key, value)
|
||||
if (value === undefined) localStorage.removeItem(key)
|
||||
else localStorage.setItem(key, value)
|
||||
} catch {
|
||||
/* storage kapalıysa yoksay */
|
||||
}
|
||||
|
|
@ -240,9 +248,13 @@ function recalculate(
|
|||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Sürüm bilgisini application-configuration yanıtından alır.
|
||||
* Sürüm bilgisini application-configuration yanıtından alır ve yalnızca bu
|
||||
* modülün GÖRÜNTÜLEME durumunu (güncelleme penceresi) besler.
|
||||
*
|
||||
* Kalıcı sürüm takibi — son görülen sürüm ve changelog bayrağı — burada değil,
|
||||
* store'un `locale` diliminde tutulur (`setUiVersion`). Bu modül entry chunk'ta
|
||||
* olduğu için store'a bağımlı olmamalı; bayrağı yazan taraf store'dur.
|
||||
*
|
||||
* Store, config'i her yüklediğinde bunu çağırır; ayrı bir sürüm isteği yoktur.
|
||||
* Yük şekli: `extraProperties.changeLogs = { uiVersion, tags[] }`.
|
||||
*/
|
||||
export const setAppVersionInfo = (raw: unknown) => {
|
||||
|
|
@ -253,29 +265,6 @@ export const setAppVersionInfo = (raw: unknown) => {
|
|||
currentVersion: data.uiVersion,
|
||||
changeLog: Array.isArray(data.tags) ? data.tags.filter(Boolean) : [],
|
||||
})
|
||||
|
||||
markVersionSeen(data.uiVersion)
|
||||
}
|
||||
|
||||
/** Login sonrası changelog yönlendirmesi için bayrak. */
|
||||
export const hasPendingChangelog = () => !!readStorage(PENDING_CHANGELOG_KEY)
|
||||
|
||||
export const clearPendingChangelog = () => writeStorage(PENDING_CHANGELOG_KEY, undefined)
|
||||
|
||||
/**
|
||||
* Sürüm numarası son görülenden farklıysa changelog bayrağını yazar; kullanıcı
|
||||
* bir sonraki girişinde ChangeLog ekranına yönlendirilir.
|
||||
*
|
||||
* Tetikleyici bilerek BUNDLE DEĞİL, backend'den gelen sürümdür: yalnızca yeni
|
||||
* bir sürüm notu (git tag) eklendiğinde de — hiçbir bileşen değişmemiş olsa
|
||||
* bile — kullanıcı değişiklikleri görür. İlk açılışta (kayıt yokken) bayrak
|
||||
* yazılmaz, yalnızca mevcut sürüm not edilir.
|
||||
*/
|
||||
function markVersionSeen(version: string) {
|
||||
const lastSeen = readStorage(LAST_VERSION_KEY)
|
||||
if (lastSeen === version) return
|
||||
if (lastSeen) writeStorage(PENDING_CHANGELOG_KEY, version)
|
||||
writeStorage(LAST_VERSION_KEY, version)
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -291,6 +280,7 @@ let lastCheckAt = 0
|
|||
let pollTimer: number | undefined
|
||||
let activationRetryTimer: number | undefined
|
||||
let activationTimeoutTimer: number | undefined
|
||||
let activationHandoffTimer: number | undefined
|
||||
|
||||
const watchedRegistrations = new WeakSet<ServiceWorkerRegistration>()
|
||||
const watchedWorkers = new WeakSet<ServiceWorker>()
|
||||
|
|
@ -298,8 +288,39 @@ const watchedWorkers = new WeakSet<ServiceWorker>()
|
|||
function clearActivationTimers() {
|
||||
window.clearTimeout(activationRetryTimer)
|
||||
window.clearTimeout(activationTimeoutTimer)
|
||||
window.clearTimeout(activationHandoffTimer)
|
||||
activationRetryTimer = undefined
|
||||
activationTimeoutTimer = undefined
|
||||
activationHandoffTimer = undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* İndirme bitti ('ready') ama geçişi kimse başlatmadıysa devreye girer.
|
||||
*
|
||||
* Normal akışta geçişi worker'ın `installed` olayı tetikler. O olay kaçabiliyor:
|
||||
* dinleyici yalnızca `reg.installing` görüldüğünde kuruluyor ve sayfa hard reload
|
||||
* ile açıldığında kayıt zaten `waiting` durumunda olabiliyor. Watchdog olmadan
|
||||
* durum sonsuza kadar 'ready'de kalıyordu — ekran "yeni sürüme geçiliyor"da
|
||||
* donuyor, zaman aşımı da yoktu çünkü sayaçlar yalnızca aktivasyon başladıktan
|
||||
* sonra kuruluyor.
|
||||
*/
|
||||
function scheduleActivationHandoff() {
|
||||
if (activationHandoffTimer || activating || reloading) return
|
||||
|
||||
activationHandoffTimer = window.setTimeout(() => {
|
||||
activationHandoffTimer = undefined
|
||||
if (activating || reloading) return
|
||||
|
||||
if (registration?.waiting) {
|
||||
activateWaitingWorker()
|
||||
return
|
||||
}
|
||||
|
||||
// Bekleyen worker yok: geçiş sessizce kaybolmuş. Kabuk cache'i tazelenerek
|
||||
// yenilenir, aksi halde aynı eski index.html geri gelir.
|
||||
console.warn('Yeni sürüme geçiş tetiklenmedi, sayfa yenileniyor.')
|
||||
void reloadWithFreshShell()
|
||||
}, ACTIVATION_HANDOFF_TIMEOUT)
|
||||
}
|
||||
|
||||
function reloadForUpdate() {
|
||||
|
|
@ -539,6 +560,12 @@ function handleWorkerMessage(event: MessageEvent) {
|
|||
*/
|
||||
const transitions = controlled && data.phase !== 'warmup'
|
||||
|
||||
// Isıtma kurulumla aynı anda koşabiliyor (eski aktif worker ısıtırken yenisi
|
||||
// kuruluyor). İki oturumun dosya listeleri ayrı index uzayında olduğu için
|
||||
// birleşince satırlar tekrarlanıyor ve toplam boyut aşıyordu; geçiş sürecinde
|
||||
// ısıtma mesajları yok sayılır.
|
||||
if (data.phase === 'warmup' && (activating || state.status === 'ready')) return
|
||||
|
||||
const totals = { loadedBytes: data.loadedBytes, totalBytes: data.totalBytes }
|
||||
|
||||
switch (data.type) {
|
||||
|
|
@ -575,7 +602,8 @@ function handleWorkerMessage(event: MessageEvent) {
|
|||
visible: true,
|
||||
percent: 100,
|
||||
})
|
||||
if (!transitions) hideDialogLater()
|
||||
if (transitions) scheduleActivationHandoff()
|
||||
else hideDialogLater()
|
||||
break
|
||||
case 'SW_PRECACHE_ERROR':
|
||||
if (!transitions) {
|
||||
|
|
@ -714,8 +742,14 @@ function scheduleWarmup(reg: ServiceWorkerRegistration) {
|
|||
|
||||
function trackInstallingWorker(reg: ServiceWorkerRegistration) {
|
||||
const worker = reg.installing
|
||||
// Controller yoksa bu ilk kurulumdur; kullanıcıya güncelleme demek yanlış olur.
|
||||
if (!worker || !navigator.serviceWorker.controller || watchedWorkers.has(worker)) return
|
||||
if (!worker || watchedWorkers.has(worker)) return
|
||||
|
||||
// İlk kurulumda kullanıcıya "güncelleme" demek yanlış olur. Ölçüt yalnızca
|
||||
// `controller` olamaz: hard reload'da sayfa kontrolsüz açılabiliyor, ama ortada
|
||||
// güncellenen bir kayıt (aktif worker) var. Bu ayrım yapılmazsa dinleyici hiç
|
||||
// kurulmuyor ve geçişi tetikleyecek `installed` olayı kaçıyordu.
|
||||
const isUpdate = !!navigator.serviceWorker.controller || !!reg.active
|
||||
if (!isUpdate) return
|
||||
|
||||
watchedWorkers.add(worker)
|
||||
void showUpdateDialog('downloading')
|
||||
|
|
@ -776,8 +810,8 @@ function startWatchers() {
|
|||
function handleChunkLoadFailures() {
|
||||
window.addEventListener('vite:preloadError', (event) => {
|
||||
if (reloading) return
|
||||
if (readStorage(CHUNK_RELOAD_GUARD_KEY, true)) return
|
||||
writeStorage(CHUNK_RELOAD_GUARD_KEY, '1', true)
|
||||
if (readStorage(CHUNK_RELOAD_GUARD_KEY)) return
|
||||
writeStorage(CHUNK_RELOAD_GUARD_KEY, '1')
|
||||
event.preventDefault()
|
||||
console.warn('Eksik chunk tespit edildi, sayfa yenileniyor.')
|
||||
reloading = true
|
||||
|
|
@ -785,7 +819,7 @@ function handleChunkLoadFailures() {
|
|||
})
|
||||
|
||||
// Sayfa sorunsuz açıldıysa koruma bayrağını temizle.
|
||||
window.setTimeout(() => writeStorage(CHUNK_RELOAD_GUARD_KEY, undefined, true), 10_000)
|
||||
window.setTimeout(() => writeStorage(CHUNK_RELOAD_GUARD_KEY, undefined), 10_000)
|
||||
}
|
||||
|
||||
/** Kapalıyken önceki oturumlardan kalan kayıtları temizler. */
|
||||
|
|
|
|||
Loading…
Reference in a new issue