87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
/**
|
||
* Tarayıcının "uygulamayı yükle" akışı (PWA install).
|
||
*
|
||
* `beforeinstallprompt` sayfa yüklenirken erken tetiklenir; React ağacı mount
|
||
* olmadan yakalanmazsa olay kaybolur ve buton hiç görünmez. Bu yüzden modül
|
||
* import edilir edilmez dinleyiciler kurulur (dosya ~1 KB, entry'de kalması
|
||
* sorun değil).
|
||
*/
|
||
|
||
type InstallPromptEvent = Event & {
|
||
prompt: () => Promise<void>
|
||
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
|
||
}
|
||
|
||
export type InstallState = {
|
||
/** Tarayıcı kurulum teklifi verdi ve uygulama henüz kurulu değil. */
|
||
canInstall: boolean
|
||
/** Uygulama standalone modda çalışıyor (yani kurulu). */
|
||
installed: boolean
|
||
}
|
||
|
||
let deferredPrompt: InstallPromptEvent | undefined
|
||
|
||
const isStandalone = () => {
|
||
try {
|
||
return (
|
||
window.matchMedia('(display-mode: standalone)').matches ||
|
||
window.matchMedia('(display-mode: minimal-ui)').matches ||
|
||
(navigator as unknown as { standalone?: boolean }).standalone === true
|
||
)
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
let state: InstallState = { canInstall: false, installed: isStandalone() }
|
||
|
||
const listeners = new Set<(value: InstallState) => void>()
|
||
|
||
export const getInstallState = () => state
|
||
|
||
export const subscribeInstallState = (listener: (value: InstallState) => void) => {
|
||
listeners.add(listener)
|
||
return () => listeners.delete(listener)
|
||
}
|
||
|
||
function setState(patch: Partial<InstallState>) {
|
||
state = { ...state, ...patch }
|
||
listeners.forEach((listener) => listener(state))
|
||
}
|
||
|
||
export const promptAppInstall = async (): Promise<'accepted' | 'dismissed' | 'unavailable'> => {
|
||
const event = deferredPrompt
|
||
if (!event) return 'unavailable'
|
||
// Yakalanan olay tek kullanımlıktır.
|
||
deferredPrompt = undefined
|
||
setState({ canInstall: false })
|
||
try {
|
||
await event.prompt()
|
||
const { outcome } = await event.userChoice
|
||
if (outcome === 'accepted') setState({ installed: true })
|
||
return outcome
|
||
} catch (error) {
|
||
console.warn('Uygulama kurulumu başlatılamadı.', error)
|
||
return 'unavailable'
|
||
}
|
||
}
|
||
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('beforeinstallprompt', (event) => {
|
||
// Varsayılan mini bar yerine uygulama içindeki butonu kullanıyoruz.
|
||
event.preventDefault()
|
||
deferredPrompt = event as InstallPromptEvent
|
||
setState({ canInstall: !isStandalone(), installed: isStandalone() })
|
||
})
|
||
|
||
window.addEventListener('appinstalled', () => {
|
||
deferredPrompt = undefined
|
||
setState({ canInstall: false, installed: true })
|
||
})
|
||
|
||
window
|
||
.matchMedia('(display-mode: standalone)')
|
||
.addEventListener?.('change', (event) =>
|
||
setState({ installed: event.matches, canInstall: event.matches ? false : state.canInstall }),
|
||
)
|
||
}
|