@djangocfg/layouts 2.1.35 → 2.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/layouts/AppLayout/BaseApp.tsx +31 -25
- package/src/layouts/shared/types.ts +36 -0
- package/src/snippets/McpChat/context/ChatContext.tsx +9 -0
- package/src/snippets/PWA/@docs/research.md +576 -0
- package/src/snippets/PWA/@refactoring/ARCHITECTURE_ANALYSIS.md +1179 -0
- package/src/snippets/PWA/@refactoring/EXECUTIVE_SUMMARY.md +271 -0
- package/src/snippets/PWA/@refactoring/README.md +204 -0
- package/src/snippets/PWA/@refactoring/REFACTORING_PROPOSALS.md +1109 -0
- package/src/snippets/PWA/@refactoring2/COMPARISON-WITH-NEXTJS.md +718 -0
- package/src/snippets/PWA/@refactoring2/P1-FIXES-COMPLETED.md +188 -0
- package/src/snippets/PWA/@refactoring2/POST-P0-ANALYSIS.md +362 -0
- package/src/snippets/PWA/@refactoring2/README.md +85 -0
- package/src/snippets/PWA/@refactoring2/RECOMMENDATIONS.md +1321 -0
- package/src/snippets/PWA/@refactoring2/REMAINING-ISSUES.md +557 -0
- package/src/snippets/PWA/README.md +387 -0
- package/src/snippets/PWA/components/A2HSHint.tsx +226 -0
- package/src/snippets/PWA/components/IOSGuide.tsx +29 -0
- package/src/snippets/PWA/components/IOSGuideDrawer.tsx +101 -0
- package/src/snippets/PWA/components/IOSGuideModal.tsx +101 -0
- package/src/snippets/PWA/components/PushPrompt.tsx +165 -0
- package/src/snippets/PWA/config.ts +20 -0
- package/src/snippets/PWA/context/DjangoPushContext.tsx +105 -0
- package/src/snippets/PWA/context/InstallContext.tsx +118 -0
- package/src/snippets/PWA/context/PushContext.tsx +156 -0
- package/src/snippets/PWA/hooks/useDjangoPush.ts +277 -0
- package/src/snippets/PWA/hooks/useInstallPrompt.ts +164 -0
- package/src/snippets/PWA/hooks/useIsPWA.ts +115 -0
- package/src/snippets/PWA/hooks/usePushNotifications.ts +205 -0
- package/src/snippets/PWA/index.ts +95 -0
- package/src/snippets/PWA/types/components.ts +101 -0
- package/src/snippets/PWA/types/index.ts +26 -0
- package/src/snippets/PWA/types/install.ts +38 -0
- package/src/snippets/PWA/types/platform.ts +29 -0
- package/src/snippets/PWA/types/push.ts +21 -0
- package/src/snippets/PWA/utils/localStorage.ts +203 -0
- package/src/snippets/PWA/utils/logger.ts +149 -0
- package/src/snippets/PWA/utils/platform.ts +151 -0
- package/src/snippets/PWA/utils/vapid.ts +226 -0
- package/src/snippets/index.ts +30 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Push Notifications Hook
|
|
5
|
+
*
|
|
6
|
+
* Manages push notification subscription state
|
|
7
|
+
* Integrates with @djangocfg/nextjs/pwa
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useEffect } from 'react';
|
|
11
|
+
import type { PushNotificationState, PushNotificationOptions } from '../types';
|
|
12
|
+
import { pwaLogger } from '../utils/logger';
|
|
13
|
+
import { urlBase64ToUint8Array, VapidKeyError } from '../utils/vapid';
|
|
14
|
+
|
|
15
|
+
export function usePushNotifications(options?: PushNotificationOptions) {
|
|
16
|
+
const [state, setState] = useState<PushNotificationState>({
|
|
17
|
+
isSupported: false,
|
|
18
|
+
permission: 'default',
|
|
19
|
+
isSubscribed: false,
|
|
20
|
+
subscription: null,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Check if push notifications are supported
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (typeof window === 'undefined') return;
|
|
26
|
+
|
|
27
|
+
const isSupported = 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
|
|
28
|
+
|
|
29
|
+
setState((prev) => ({
|
|
30
|
+
...prev,
|
|
31
|
+
isSupported,
|
|
32
|
+
permission: isSupported ? Notification.permission : 'denied',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Check existing subscription
|
|
36
|
+
if (isSupported) {
|
|
37
|
+
navigator.serviceWorker.ready
|
|
38
|
+
.then((registration) => registration.pushManager.getSubscription())
|
|
39
|
+
.then((subscription) => {
|
|
40
|
+
setState((prev) => ({
|
|
41
|
+
...prev,
|
|
42
|
+
isSubscribed: !!subscription,
|
|
43
|
+
subscription,
|
|
44
|
+
}));
|
|
45
|
+
})
|
|
46
|
+
.catch((error) => {
|
|
47
|
+
pwaLogger.error('[usePushNotifications] Failed to get subscription:', error);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const subscribe = async (): Promise<boolean> => {
|
|
53
|
+
if (!state.isSupported) {
|
|
54
|
+
pwaLogger.warn('[usePushNotifications] Push notifications not supported');
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!options?.vapidPublicKey) {
|
|
59
|
+
pwaLogger.error('[usePushNotifications] VAPID public key required');
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Pre-flight check: Test network connectivity
|
|
65
|
+
pwaLogger.debug('[usePushNotifications] Running pre-flight checks...');
|
|
66
|
+
|
|
67
|
+
// Check if we're online
|
|
68
|
+
if (!navigator.onLine) {
|
|
69
|
+
pwaLogger.error('[usePushNotifications] No internet connection');
|
|
70
|
+
throw new Error('No internet connection. Please check your network and try again.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Request permission
|
|
74
|
+
const permission = await Notification.requestPermission();
|
|
75
|
+
setState((prev) => ({ ...prev, permission }));
|
|
76
|
+
|
|
77
|
+
if (permission !== 'granted') {
|
|
78
|
+
pwaLogger.warn('[usePushNotifications] Permission not granted:', permission);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Subscribe to push
|
|
83
|
+
const registration = await navigator.serviceWorker.ready;
|
|
84
|
+
|
|
85
|
+
// Convert and validate VAPID key
|
|
86
|
+
let applicationServerKey: Uint8Array;
|
|
87
|
+
try {
|
|
88
|
+
pwaLogger.debug('[usePushNotifications] Converting VAPID key...');
|
|
89
|
+
applicationServerKey = urlBase64ToUint8Array(options.vapidPublicKey);
|
|
90
|
+
pwaLogger.info('[usePushNotifications] VAPID key validated successfully');
|
|
91
|
+
} catch (e) {
|
|
92
|
+
if (e instanceof VapidKeyError) {
|
|
93
|
+
pwaLogger.error(`[usePushNotifications] Invalid VAPID key: ${e.message} (code: ${e.code})`);
|
|
94
|
+
} else {
|
|
95
|
+
pwaLogger.error('[usePushNotifications] Failed to convert VAPID key:', e);
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Diagnostic logging (only in debug mode)
|
|
101
|
+
pwaLogger.debug('[usePushNotifications] Service Worker state:', {
|
|
102
|
+
controller: navigator.serviceWorker.controller ? 'active' : 'none',
|
|
103
|
+
registrationActive: registration.active ? 'yes' : 'no',
|
|
104
|
+
permission: Notification.permission,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Check for existing subscription and unsubscribe
|
|
108
|
+
const existingSub = await registration.pushManager.getSubscription();
|
|
109
|
+
if (existingSub) {
|
|
110
|
+
pwaLogger.debug('[usePushNotifications] Unsubscribing from existing subscription...');
|
|
111
|
+
await existingSub.unsubscribe();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Prepare subscription options
|
|
115
|
+
const subscribeOptions = {
|
|
116
|
+
userVisibleOnly: true,
|
|
117
|
+
applicationServerKey: applicationServerKey as unknown as BufferSource,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
pwaLogger.debug('[usePushNotifications] Subscribing with VAPID key...');
|
|
121
|
+
|
|
122
|
+
// Attempt subscribe
|
|
123
|
+
const subscription = await registration.pushManager.subscribe(subscribeOptions);
|
|
124
|
+
|
|
125
|
+
// Send subscription to server
|
|
126
|
+
if (options.subscribeEndpoint) {
|
|
127
|
+
await fetch(options.subscribeEndpoint, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/json' },
|
|
130
|
+
body: JSON.stringify(subscription),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setState((prev) => ({
|
|
135
|
+
...prev,
|
|
136
|
+
isSubscribed: true,
|
|
137
|
+
subscription,
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
pwaLogger.success('[usePushNotifications] Successfully subscribed to push notifications');
|
|
141
|
+
return true;
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
pwaLogger.error('[usePushNotifications] Subscribe failed:', error);
|
|
144
|
+
|
|
145
|
+
// Specific diagnostic for push service errors
|
|
146
|
+
if (error.name === 'AbortError' || error.message?.includes('push service error')) {
|
|
147
|
+
pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
148
|
+
pwaLogger.error('❌ PUSH SERVICE ERROR - Cannot connect to FCM');
|
|
149
|
+
pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
150
|
+
pwaLogger.error('');
|
|
151
|
+
pwaLogger.error('🔍 This is NOT a code bug - it\'s a network/security block.');
|
|
152
|
+
pwaLogger.error('');
|
|
153
|
+
pwaLogger.error('✅ Quick Fixes (try in order):');
|
|
154
|
+
pwaLogger.error(' 1. Disable VPN/Proxy and refresh page');
|
|
155
|
+
pwaLogger.error(' 2. Open Incognito window (Cmd+Shift+N)');
|
|
156
|
+
pwaLogger.error(' 3. Try different browser (Safari, Firefox)');
|
|
157
|
+
pwaLogger.error(' 4. Use mobile hotspot instead of WiFi');
|
|
158
|
+
pwaLogger.error('');
|
|
159
|
+
pwaLogger.error('🔧 Technical Details:');
|
|
160
|
+
pwaLogger.error(' • Browser tries to connect to FCM (ports 5228-5230)');
|
|
161
|
+
pwaLogger.error(' • VPN/Firewall/Privacy settings may block these ports');
|
|
162
|
+
pwaLogger.error(' • Check browser console for "AbortError" details');
|
|
163
|
+
pwaLogger.error('');
|
|
164
|
+
pwaLogger.error('📚 Learn more: https://web.dev/push-notifications-overview/');
|
|
165
|
+
pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
166
|
+
} else if (error.message?.includes('No internet connection')) {
|
|
167
|
+
pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
168
|
+
pwaLogger.error('❌ NO INTERNET CONNECTION');
|
|
169
|
+
pwaLogger.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
170
|
+
pwaLogger.error('Please check your network connection and try again.');
|
|
171
|
+
} else {
|
|
172
|
+
pwaLogger.error('Unknown error:', error.name, error.message);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const unsubscribe = async (): Promise<boolean> => {
|
|
180
|
+
if (!state.subscription) {
|
|
181
|
+
pwaLogger.warn('[usePushNotifications] No active subscription to unsubscribe');
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await state.subscription.unsubscribe();
|
|
187
|
+
setState((prev) => ({
|
|
188
|
+
...prev,
|
|
189
|
+
isSubscribed: false,
|
|
190
|
+
subscription: null,
|
|
191
|
+
}));
|
|
192
|
+
pwaLogger.info('[usePushNotifications] Successfully unsubscribed from push notifications');
|
|
193
|
+
return true;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
pwaLogger.error('[usePushNotifications] Unsubscribe failed:', error);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
...state,
|
|
202
|
+
subscribe,
|
|
203
|
+
unsubscribe,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PWA Install Flow (Simplified)
|
|
3
|
+
*
|
|
4
|
+
* Ultra-simple PWA installation for Cmdop
|
|
5
|
+
* No tracking, no metrics, no complexity — just install
|
|
6
|
+
*
|
|
7
|
+
* @example Basic usage
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { InstallProvider, A2HSHint } from './PwaInstall';
|
|
10
|
+
* import { DEFAULT_VAPID_PUBLIC_KEY } from './config';
|
|
11
|
+
*
|
|
12
|
+
* export default function Layout({ children }) {
|
|
13
|
+
* return (
|
|
14
|
+
* <InstallProvider>
|
|
15
|
+
* {children}
|
|
16
|
+
* <A2HSHint resetAfterDays={3} />
|
|
17
|
+
* </InstallProvider>
|
|
18
|
+
* );
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// Main API
|
|
24
|
+
export { PwaProvider, useInstall } from './context/InstallContext';
|
|
25
|
+
export { PushProvider, usePush } from './context/PushContext';
|
|
26
|
+
export type { PushMessage, PushContextValue } from './context/PushContext';
|
|
27
|
+
|
|
28
|
+
// Django Integration
|
|
29
|
+
export { DjangoPushProvider, useDjangoPushContext } from './context/DjangoPushContext';
|
|
30
|
+
export { A2HSHint } from './components/A2HSHint';
|
|
31
|
+
export { PushPrompt } from './components/PushPrompt';
|
|
32
|
+
export { DEFAULT_VAPID_PUBLIC_KEY } from './config';
|
|
33
|
+
|
|
34
|
+
// Hooks
|
|
35
|
+
export { useIsPWA, clearIsPWACache, type UseIsPWAOptions } from './hooks/useIsPWA';
|
|
36
|
+
export { usePushNotifications } from './hooks/usePushNotifications';
|
|
37
|
+
export { useDjangoPush } from './hooks/useDjangoPush';
|
|
38
|
+
|
|
39
|
+
// Utilities
|
|
40
|
+
export {
|
|
41
|
+
isStandalone,
|
|
42
|
+
isStandaloneReliable,
|
|
43
|
+
isMobileDevice,
|
|
44
|
+
hasValidManifest,
|
|
45
|
+
getDisplayMode,
|
|
46
|
+
onDisplayModeChange,
|
|
47
|
+
} from './utils/platform';
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
pwaLogger,
|
|
51
|
+
enablePWADebug,
|
|
52
|
+
disablePWADebug,
|
|
53
|
+
isPWADebugEnabled,
|
|
54
|
+
} from './utils/logger';
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
urlBase64ToUint8Array,
|
|
58
|
+
isValidVapidKey,
|
|
59
|
+
getVapidKeyInfo,
|
|
60
|
+
safeUrlBase64ToUint8Array,
|
|
61
|
+
VapidKeyError,
|
|
62
|
+
type VapidKeyErrorCode,
|
|
63
|
+
} from './utils/vapid';
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
STORAGE_KEYS,
|
|
67
|
+
markA2HSDismissed,
|
|
68
|
+
markPushDismissed,
|
|
69
|
+
isA2HSDismissedRecently,
|
|
70
|
+
isPushDismissedRecently,
|
|
71
|
+
clearAllPWAData,
|
|
72
|
+
} from './utils/localStorage';
|
|
73
|
+
|
|
74
|
+
// Types - Platform
|
|
75
|
+
export type { PlatformInfo } from './types';
|
|
76
|
+
|
|
77
|
+
// Types - Install
|
|
78
|
+
export type {
|
|
79
|
+
InstallPromptState,
|
|
80
|
+
BeforeInstallPromptEvent,
|
|
81
|
+
InstallOutcome,
|
|
82
|
+
IOSGuideState,
|
|
83
|
+
} from './types';
|
|
84
|
+
|
|
85
|
+
// Types - Push Notifications
|
|
86
|
+
export type { PushNotificationState, PushNotificationOptions } from './types';
|
|
87
|
+
|
|
88
|
+
// Types - Components
|
|
89
|
+
export type {
|
|
90
|
+
InstallContextType,
|
|
91
|
+
InstallManagerProps,
|
|
92
|
+
AndroidInstallButtonProps,
|
|
93
|
+
IOSGuideModalProps,
|
|
94
|
+
InstallStep,
|
|
95
|
+
} from './types';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Props Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PlatformInfo } from './platform';
|
|
6
|
+
import type { InstallOutcome } from './install';
|
|
7
|
+
import type { PushNotificationState } from './push';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Install context type
|
|
11
|
+
*/
|
|
12
|
+
export interface InstallContextType {
|
|
13
|
+
// State
|
|
14
|
+
platform: PlatformInfo;
|
|
15
|
+
isInstalled: boolean;
|
|
16
|
+
canPrompt: boolean;
|
|
17
|
+
|
|
18
|
+
// iOS Guide
|
|
19
|
+
showIOSGuide: boolean;
|
|
20
|
+
setShowIOSGuide: (show: boolean) => void;
|
|
21
|
+
|
|
22
|
+
// Actions
|
|
23
|
+
promptInstall: () => Promise<InstallOutcome>;
|
|
24
|
+
dismissIOSGuide: () => void;
|
|
25
|
+
|
|
26
|
+
// Push Notifications
|
|
27
|
+
pushState: PushNotificationState;
|
|
28
|
+
subscribeToPush: () => Promise<boolean>;
|
|
29
|
+
unsubscribeFromPush: () => Promise<boolean>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Install manager props
|
|
34
|
+
*/
|
|
35
|
+
export interface InstallManagerProps {
|
|
36
|
+
/**
|
|
37
|
+
* Delay before showing iOS guide (ms)
|
|
38
|
+
* @default 2000
|
|
39
|
+
*/
|
|
40
|
+
delayMs?: number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Number of days before re-showing dismissed guide
|
|
44
|
+
* @default 7
|
|
45
|
+
*/
|
|
46
|
+
resetDays?: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Custom class name for install button
|
|
50
|
+
*/
|
|
51
|
+
buttonClassName?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Custom button text
|
|
55
|
+
*/
|
|
56
|
+
buttonText?: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Force show install UI (ignores platform detection)
|
|
60
|
+
* Useful for testing on desktop in development
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
forceShow?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Callback when install is successful
|
|
67
|
+
*/
|
|
68
|
+
onInstallSuccess?: () => void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Callback when install is dismissed
|
|
72
|
+
*/
|
|
73
|
+
onInstallDismiss?: () => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Android install button props
|
|
78
|
+
*/
|
|
79
|
+
export interface AndroidInstallButtonProps {
|
|
80
|
+
onInstall: () => Promise<InstallOutcome>;
|
|
81
|
+
className?: string;
|
|
82
|
+
text?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* iOS guide modal props
|
|
87
|
+
*/
|
|
88
|
+
export interface IOSGuideModalProps {
|
|
89
|
+
onDismiss: () => void;
|
|
90
|
+
open?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Install step for iOS guide
|
|
95
|
+
*/
|
|
96
|
+
export interface InstallStep {
|
|
97
|
+
number: number;
|
|
98
|
+
title: string;
|
|
99
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
100
|
+
description: string;
|
|
101
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PWA Types - Central Export
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Platform
|
|
6
|
+
export type { PlatformInfo } from './platform';
|
|
7
|
+
|
|
8
|
+
// Install
|
|
9
|
+
export type {
|
|
10
|
+
InstallPromptState,
|
|
11
|
+
BeforeInstallPromptEvent,
|
|
12
|
+
InstallOutcome,
|
|
13
|
+
IOSGuideState,
|
|
14
|
+
} from './install';
|
|
15
|
+
|
|
16
|
+
// Push Notifications
|
|
17
|
+
export type { PushNotificationState, PushNotificationOptions } from './push';
|
|
18
|
+
|
|
19
|
+
// Components
|
|
20
|
+
export type {
|
|
21
|
+
InstallContextType,
|
|
22
|
+
InstallManagerProps,
|
|
23
|
+
AndroidInstallButtonProps,
|
|
24
|
+
IOSGuideModalProps,
|
|
25
|
+
InstallStep,
|
|
26
|
+
} from './components';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PWA Installation Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Install prompt state
|
|
7
|
+
*/
|
|
8
|
+
export interface InstallPromptState {
|
|
9
|
+
isIOS: boolean;
|
|
10
|
+
isAndroid: boolean;
|
|
11
|
+
isSafari: boolean;
|
|
12
|
+
isChrome: boolean;
|
|
13
|
+
isInstalled: boolean;
|
|
14
|
+
canPrompt: boolean;
|
|
15
|
+
deferredPrompt: BeforeInstallPromptEvent | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* BeforeInstallPrompt event (Android Chrome)
|
|
20
|
+
*/
|
|
21
|
+
export interface BeforeInstallPromptEvent extends Event {
|
|
22
|
+
prompt: () => Promise<void>;
|
|
23
|
+
userChoice: Promise<{ outcome: 'accepted' | 'dismissed'; platform: string }>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Install outcome
|
|
28
|
+
*/
|
|
29
|
+
export type InstallOutcome = 'accepted' | 'dismissed' | null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* iOS guide dismissal state
|
|
33
|
+
*/
|
|
34
|
+
export interface IOSGuideState {
|
|
35
|
+
dismissed: boolean;
|
|
36
|
+
dismissedAt: number | null;
|
|
37
|
+
shouldShow: boolean;
|
|
38
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Detection Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Platform detection result
|
|
7
|
+
*/
|
|
8
|
+
export interface PlatformInfo {
|
|
9
|
+
// Operating System
|
|
10
|
+
isIOS: boolean;
|
|
11
|
+
isAndroid: boolean;
|
|
12
|
+
isDesktop: boolean;
|
|
13
|
+
|
|
14
|
+
// Browser
|
|
15
|
+
isSafari: boolean;
|
|
16
|
+
isChrome: boolean;
|
|
17
|
+
isEdge: boolean;
|
|
18
|
+
isFirefox: boolean;
|
|
19
|
+
|
|
20
|
+
// Installation State
|
|
21
|
+
isStandalone: boolean;
|
|
22
|
+
|
|
23
|
+
// Capability
|
|
24
|
+
canPrompt: boolean;
|
|
25
|
+
|
|
26
|
+
// Composite checks
|
|
27
|
+
shouldShowAndroidPrompt: boolean;
|
|
28
|
+
shouldShowIOSGuide: boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push Notification Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push notification state
|
|
7
|
+
*/
|
|
8
|
+
export interface PushNotificationState {
|
|
9
|
+
isSupported: boolean;
|
|
10
|
+
permission: NotificationPermission;
|
|
11
|
+
isSubscribed: boolean;
|
|
12
|
+
subscription: PushSubscription | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Push notification options
|
|
17
|
+
*/
|
|
18
|
+
export interface PushNotificationOptions {
|
|
19
|
+
vapidPublicKey: string;
|
|
20
|
+
subscribeEndpoint?: string;
|
|
21
|
+
}
|