diff --git a/browserconfig.xml b/browserconfig.xml new file mode 100644 index 0000000..8f4020a --- /dev/null +++ b/browserconfig.xml @@ -0,0 +1,17 @@ + + + + + + + + + #ff6b35 + + + + 30 + 1 + + + \ No newline at end of file diff --git a/index.html b/index.html index 636d491..8c08a92 100644 --- a/index.html +++ b/index.html @@ -13,10 +13,61 @@ media-src 'none'; object-src 'none'; frame-src 'none';"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -112,7 +163,7 @@ icon: "fas fa-shield-halved", color: "orange", title: "12-Layer Military Security", - description: "Revolutionary defense system with ECDH P-384 + AES-GCM 256 + ECDSA. Enhanced Security Edition v4.0 provides military-grade protection exceeding government standards." + description: "Revolutionary defense system with ECDH P-384 + AES-GCM 256 + ECDSA. Enhanced Security Edition v4.01.212 provides military-grade protection exceeding government standards." }, { icon: "fas fa-bolt", @@ -462,7 +513,7 @@ Enhanced Security Edition Comparison

- SecureBit.chat v4.0 Enhanced Security Edition vs leading secure messengers + SecureBit.chat v4.01.212 Enhanced Security Edition vs leading secure messengers

๐Ÿ† @@ -608,7 +659,7 @@

- SecureBit.chat v4.0 Enhanced Security Edition Summary + SecureBit.chat v4.01.212 Enhanced Security Edition Summary

SecureBit.chat dominates in 11 out of 15 security categories, establishing itself as the most secure P2P messenger available. @@ -3581,5 +3632,41 @@ if (window.DEBUG_MODE) { console.log('โœ… Global timer management functions loaded'); } + + + + + \ No newline at end of file diff --git a/logo/icon-144x144.png b/logo/icon-144x144.png new file mode 100644 index 0000000..0d0312a Binary files /dev/null and b/logo/icon-144x144.png differ diff --git a/logo/pwa/android/android-launchericon-144-144.png b/logo/pwa/android/android-launchericon-144-144.png new file mode 100644 index 0000000..0d0312a Binary files /dev/null and b/logo/pwa/android/android-launchericon-144-144.png differ diff --git a/logo/pwa/android/android-launchericon-192-192.png b/logo/pwa/android/android-launchericon-192-192.png new file mode 100644 index 0000000..d985d80 Binary files /dev/null and b/logo/pwa/android/android-launchericon-192-192.png differ diff --git a/logo/pwa/android/android-launchericon-48-48.png b/logo/pwa/android/android-launchericon-48-48.png new file mode 100644 index 0000000..b4ab75b Binary files /dev/null and b/logo/pwa/android/android-launchericon-48-48.png differ diff --git a/logo/pwa/android/android-launchericon-512-512.png b/logo/pwa/android/android-launchericon-512-512.png new file mode 100644 index 0000000..8c4b6d0 Binary files /dev/null and b/logo/pwa/android/android-launchericon-512-512.png differ diff --git a/logo/pwa/android/android-launchericon-72-72.png b/logo/pwa/android/android-launchericon-72-72.png new file mode 100644 index 0000000..831e41c Binary files /dev/null and b/logo/pwa/android/android-launchericon-72-72.png differ diff --git a/logo/pwa/android/android-launchericon-96-96.png b/logo/pwa/android/android-launchericon-96-96.png new file mode 100644 index 0000000..4b968f7 Binary files /dev/null and b/logo/pwa/android/android-launchericon-96-96.png differ diff --git a/logo/pwa/ios/100.png b/logo/pwa/ios/100.png new file mode 100644 index 0000000..1a909bf Binary files /dev/null and b/logo/pwa/ios/100.png differ diff --git a/logo/pwa/ios/1024.png b/logo/pwa/ios/1024.png new file mode 100644 index 0000000..db82f17 Binary files /dev/null and b/logo/pwa/ios/1024.png differ diff --git a/logo/pwa/ios/114.png b/logo/pwa/ios/114.png new file mode 100644 index 0000000..732206a Binary files /dev/null and b/logo/pwa/ios/114.png differ diff --git a/logo/pwa/ios/120.png b/logo/pwa/ios/120.png new file mode 100644 index 0000000..7d770a7 Binary files /dev/null and b/logo/pwa/ios/120.png differ diff --git a/logo/pwa/ios/128.png b/logo/pwa/ios/128.png new file mode 100644 index 0000000..24bd86c Binary files /dev/null and b/logo/pwa/ios/128.png differ diff --git a/logo/pwa/ios/144.png b/logo/pwa/ios/144.png new file mode 100644 index 0000000..0d0312a Binary files /dev/null and b/logo/pwa/ios/144.png differ diff --git a/logo/pwa/ios/152.png b/logo/pwa/ios/152.png new file mode 100644 index 0000000..1178357 Binary files /dev/null and b/logo/pwa/ios/152.png differ diff --git a/logo/pwa/ios/16.png b/logo/pwa/ios/16.png new file mode 100644 index 0000000..a7156f3 Binary files /dev/null and b/logo/pwa/ios/16.png differ diff --git a/logo/pwa/ios/167.png b/logo/pwa/ios/167.png new file mode 100644 index 0000000..9d6b32b Binary files /dev/null and b/logo/pwa/ios/167.png differ diff --git a/logo/pwa/ios/180.png b/logo/pwa/ios/180.png new file mode 100644 index 0000000..ec3d50e Binary files /dev/null and b/logo/pwa/ios/180.png differ diff --git a/logo/pwa/ios/192.png b/logo/pwa/ios/192.png new file mode 100644 index 0000000..d985d80 Binary files /dev/null and b/logo/pwa/ios/192.png differ diff --git a/logo/pwa/ios/20.png b/logo/pwa/ios/20.png new file mode 100644 index 0000000..3e336b3 Binary files /dev/null and b/logo/pwa/ios/20.png differ diff --git a/logo/pwa/ios/256.png b/logo/pwa/ios/256.png new file mode 100644 index 0000000..84fee80 Binary files /dev/null and b/logo/pwa/ios/256.png differ diff --git a/logo/pwa/ios/29.png b/logo/pwa/ios/29.png new file mode 100644 index 0000000..1496e64 Binary files /dev/null and b/logo/pwa/ios/29.png differ diff --git a/logo/pwa/ios/32.png b/logo/pwa/ios/32.png new file mode 100644 index 0000000..7de5b1d Binary files /dev/null and b/logo/pwa/ios/32.png differ diff --git a/logo/pwa/ios/40.png b/logo/pwa/ios/40.png new file mode 100644 index 0000000..c62970b Binary files /dev/null and b/logo/pwa/ios/40.png differ diff --git a/logo/pwa/ios/50.png b/logo/pwa/ios/50.png new file mode 100644 index 0000000..5a67b99 Binary files /dev/null and b/logo/pwa/ios/50.png differ diff --git a/logo/pwa/ios/512.png b/logo/pwa/ios/512.png new file mode 100644 index 0000000..8c4b6d0 Binary files /dev/null and b/logo/pwa/ios/512.png differ diff --git a/logo/pwa/ios/57.png b/logo/pwa/ios/57.png new file mode 100644 index 0000000..f42fb03 Binary files /dev/null and b/logo/pwa/ios/57.png differ diff --git a/logo/pwa/ios/58.png b/logo/pwa/ios/58.png new file mode 100644 index 0000000..886b88e Binary files /dev/null and b/logo/pwa/ios/58.png differ diff --git a/logo/pwa/ios/60.png b/logo/pwa/ios/60.png new file mode 100644 index 0000000..21f65af Binary files /dev/null and b/logo/pwa/ios/60.png differ diff --git a/logo/pwa/ios/64.png b/logo/pwa/ios/64.png new file mode 100644 index 0000000..5724ade Binary files /dev/null and b/logo/pwa/ios/64.png differ diff --git a/logo/pwa/ios/72.png b/logo/pwa/ios/72.png new file mode 100644 index 0000000..831e41c Binary files /dev/null and b/logo/pwa/ios/72.png differ diff --git a/logo/pwa/ios/76.png b/logo/pwa/ios/76.png new file mode 100644 index 0000000..d95a65e Binary files /dev/null and b/logo/pwa/ios/76.png differ diff --git a/logo/pwa/ios/80.png b/logo/pwa/ios/80.png new file mode 100644 index 0000000..afffb15 Binary files /dev/null and b/logo/pwa/ios/80.png differ diff --git a/logo/pwa/ios/87.png b/logo/pwa/ios/87.png new file mode 100644 index 0000000..2bed6ef Binary files /dev/null and b/logo/pwa/ios/87.png differ diff --git a/logo/pwa/windows11/LargeTile.scale-100.png b/logo/pwa/windows11/LargeTile.scale-100.png new file mode 100644 index 0000000..9d3360e Binary files /dev/null and b/logo/pwa/windows11/LargeTile.scale-100.png differ diff --git a/logo/pwa/windows11/LargeTile.scale-125.png b/logo/pwa/windows11/LargeTile.scale-125.png new file mode 100644 index 0000000..6089ee1 Binary files /dev/null and b/logo/pwa/windows11/LargeTile.scale-125.png differ diff --git a/logo/pwa/windows11/LargeTile.scale-150.png b/logo/pwa/windows11/LargeTile.scale-150.png new file mode 100644 index 0000000..f965dc3 Binary files /dev/null and b/logo/pwa/windows11/LargeTile.scale-150.png differ diff --git a/logo/pwa/windows11/LargeTile.scale-200.png b/logo/pwa/windows11/LargeTile.scale-200.png new file mode 100644 index 0000000..5596f34 Binary files /dev/null and b/logo/pwa/windows11/LargeTile.scale-200.png differ diff --git a/logo/pwa/windows11/LargeTile.scale-400.png b/logo/pwa/windows11/LargeTile.scale-400.png new file mode 100644 index 0000000..689eebd Binary files /dev/null and b/logo/pwa/windows11/LargeTile.scale-400.png differ diff --git a/logo/pwa/windows11/SmallTile.scale-100.png b/logo/pwa/windows11/SmallTile.scale-100.png new file mode 100644 index 0000000..851d4f0 Binary files /dev/null and b/logo/pwa/windows11/SmallTile.scale-100.png differ diff --git a/logo/pwa/windows11/SmallTile.scale-125.png b/logo/pwa/windows11/SmallTile.scale-125.png new file mode 100644 index 0000000..5f6ea43 Binary files /dev/null and b/logo/pwa/windows11/SmallTile.scale-125.png differ diff --git a/logo/pwa/windows11/SmallTile.scale-150.png b/logo/pwa/windows11/SmallTile.scale-150.png new file mode 100644 index 0000000..aa72298 Binary files /dev/null and b/logo/pwa/windows11/SmallTile.scale-150.png differ diff --git a/logo/pwa/windows11/SmallTile.scale-200.png b/logo/pwa/windows11/SmallTile.scale-200.png new file mode 100644 index 0000000..2cf66e0 Binary files /dev/null and b/logo/pwa/windows11/SmallTile.scale-200.png differ diff --git a/logo/pwa/windows11/SmallTile.scale-400.png b/logo/pwa/windows11/SmallTile.scale-400.png new file mode 100644 index 0000000..cab05de Binary files /dev/null and b/logo/pwa/windows11/SmallTile.scale-400.png differ diff --git a/logo/pwa/windows11/SplashScreen.scale-100.png b/logo/pwa/windows11/SplashScreen.scale-100.png new file mode 100644 index 0000000..60ddbf2 Binary files /dev/null and b/logo/pwa/windows11/SplashScreen.scale-100.png differ diff --git a/logo/pwa/windows11/SplashScreen.scale-125.png b/logo/pwa/windows11/SplashScreen.scale-125.png new file mode 100644 index 0000000..b2875ba Binary files /dev/null and b/logo/pwa/windows11/SplashScreen.scale-125.png differ diff --git a/logo/pwa/windows11/SplashScreen.scale-150.png b/logo/pwa/windows11/SplashScreen.scale-150.png new file mode 100644 index 0000000..2afe8ff Binary files /dev/null and b/logo/pwa/windows11/SplashScreen.scale-150.png differ diff --git a/logo/pwa/windows11/SplashScreen.scale-200.png b/logo/pwa/windows11/SplashScreen.scale-200.png new file mode 100644 index 0000000..39eb6fb Binary files /dev/null and b/logo/pwa/windows11/SplashScreen.scale-200.png differ diff --git a/logo/pwa/windows11/SplashScreen.scale-400.png b/logo/pwa/windows11/SplashScreen.scale-400.png new file mode 100644 index 0000000..0f550b5 Binary files /dev/null and b/logo/pwa/windows11/SplashScreen.scale-400.png differ diff --git a/logo/pwa/windows11/Square150x150Logo.scale-100.png b/logo/pwa/windows11/Square150x150Logo.scale-100.png new file mode 100644 index 0000000..b576bfa Binary files /dev/null and b/logo/pwa/windows11/Square150x150Logo.scale-100.png differ diff --git a/logo/pwa/windows11/Square150x150Logo.scale-125.png b/logo/pwa/windows11/Square150x150Logo.scale-125.png new file mode 100644 index 0000000..793dd52 Binary files /dev/null and b/logo/pwa/windows11/Square150x150Logo.scale-125.png differ diff --git a/logo/pwa/windows11/Square150x150Logo.scale-150.png b/logo/pwa/windows11/Square150x150Logo.scale-150.png new file mode 100644 index 0000000..a7bfb91 Binary files /dev/null and b/logo/pwa/windows11/Square150x150Logo.scale-150.png differ diff --git a/logo/pwa/windows11/Square150x150Logo.scale-200.png b/logo/pwa/windows11/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..dceac48 Binary files /dev/null and b/logo/pwa/windows11/Square150x150Logo.scale-200.png differ diff --git a/logo/pwa/windows11/Square150x150Logo.scale-400.png b/logo/pwa/windows11/Square150x150Logo.scale-400.png new file mode 100644 index 0000000..511f182 Binary files /dev/null and b/logo/pwa/windows11/Square150x150Logo.scale-400.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png new file mode 100644 index 0000000..8de6daf Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png new file mode 100644 index 0000000..cc0194b Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png new file mode 100644 index 0000000..9fa1b88 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png new file mode 100644 index 0000000..b8932b7 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png new file mode 100644 index 0000000..8389dfb Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png new file mode 100644 index 0000000..0f016b7 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png new file mode 100644 index 0000000..d6838a1 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png new file mode 100644 index 0000000..84040d8 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png new file mode 100644 index 0000000..3859f40 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png new file mode 100644 index 0000000..23d6494 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png new file mode 100644 index 0000000..1b8d76b Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png new file mode 100644 index 0000000..854cf99 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png new file mode 100644 index 0000000..4db26bc Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png new file mode 100644 index 0000000..7397ba3 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png new file mode 100644 index 0000000..2916aee Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-16.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-16.png new file mode 100644 index 0000000..8de6daf Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-16.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-20.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-20.png new file mode 100644 index 0000000..cc0194b Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-20.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-24.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-24.png new file mode 100644 index 0000000..9fa1b88 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-24.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-256.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 0000000..b8932b7 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-256.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-30.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-30.png new file mode 100644 index 0000000..8389dfb Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-30.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-32.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-32.png new file mode 100644 index 0000000..0f016b7 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-32.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-36.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-36.png new file mode 100644 index 0000000..d6838a1 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-36.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-40.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-40.png new file mode 100644 index 0000000..84040d8 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-40.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-44.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-44.png new file mode 100644 index 0000000..3859f40 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-44.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-48.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-48.png new file mode 100644 index 0000000..23d6494 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-48.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-60.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-60.png new file mode 100644 index 0000000..1b8d76b Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-60.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-64.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-64.png new file mode 100644 index 0000000..854cf99 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-64.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-72.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-72.png new file mode 100644 index 0000000..4db26bc Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-72.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-80.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-80.png new file mode 100644 index 0000000..7397ba3 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-80.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-96.png b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-96.png new file mode 100644 index 0000000..2916aee Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.altform-unplated_targetsize-96.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.scale-100.png b/logo/pwa/windows11/Square44x44Logo.scale-100.png new file mode 100644 index 0000000..3859f40 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.scale-100.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.scale-125.png b/logo/pwa/windows11/Square44x44Logo.scale-125.png new file mode 100644 index 0000000..246d54c Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.scale-125.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.scale-150.png b/logo/pwa/windows11/Square44x44Logo.scale-150.png new file mode 100644 index 0000000..3fe7b62 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.scale-150.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.scale-200.png b/logo/pwa/windows11/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..0605d51 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.scale-200.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.scale-400.png b/logo/pwa/windows11/Square44x44Logo.scale-400.png new file mode 100644 index 0000000..7afd4aa Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.scale-400.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-16.png b/logo/pwa/windows11/Square44x44Logo.targetsize-16.png new file mode 100644 index 0000000..8de6daf Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-16.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-20.png b/logo/pwa/windows11/Square44x44Logo.targetsize-20.png new file mode 100644 index 0000000..cc0194b Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-20.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-24.png b/logo/pwa/windows11/Square44x44Logo.targetsize-24.png new file mode 100644 index 0000000..9fa1b88 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-24.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-256.png b/logo/pwa/windows11/Square44x44Logo.targetsize-256.png new file mode 100644 index 0000000..b8932b7 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-256.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-30.png b/logo/pwa/windows11/Square44x44Logo.targetsize-30.png new file mode 100644 index 0000000..8389dfb Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-30.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-32.png b/logo/pwa/windows11/Square44x44Logo.targetsize-32.png new file mode 100644 index 0000000..0f016b7 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-32.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-36.png b/logo/pwa/windows11/Square44x44Logo.targetsize-36.png new file mode 100644 index 0000000..d6838a1 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-36.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-40.png b/logo/pwa/windows11/Square44x44Logo.targetsize-40.png new file mode 100644 index 0000000..84040d8 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-40.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-44.png b/logo/pwa/windows11/Square44x44Logo.targetsize-44.png new file mode 100644 index 0000000..3859f40 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-44.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-48.png b/logo/pwa/windows11/Square44x44Logo.targetsize-48.png new file mode 100644 index 0000000..23d6494 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-48.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-60.png b/logo/pwa/windows11/Square44x44Logo.targetsize-60.png new file mode 100644 index 0000000..1b8d76b Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-60.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-64.png b/logo/pwa/windows11/Square44x44Logo.targetsize-64.png new file mode 100644 index 0000000..854cf99 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-64.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-72.png b/logo/pwa/windows11/Square44x44Logo.targetsize-72.png new file mode 100644 index 0000000..4db26bc Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-72.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-80.png b/logo/pwa/windows11/Square44x44Logo.targetsize-80.png new file mode 100644 index 0000000..7397ba3 Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-80.png differ diff --git a/logo/pwa/windows11/Square44x44Logo.targetsize-96.png b/logo/pwa/windows11/Square44x44Logo.targetsize-96.png new file mode 100644 index 0000000..2916aee Binary files /dev/null and b/logo/pwa/windows11/Square44x44Logo.targetsize-96.png differ diff --git a/logo/pwa/windows11/StoreLogo.scale-100.png b/logo/pwa/windows11/StoreLogo.scale-100.png new file mode 100644 index 0000000..5a67b99 Binary files /dev/null and b/logo/pwa/windows11/StoreLogo.scale-100.png differ diff --git a/logo/pwa/windows11/StoreLogo.scale-125.png b/logo/pwa/windows11/StoreLogo.scale-125.png new file mode 100644 index 0000000..30fca14 Binary files /dev/null and b/logo/pwa/windows11/StoreLogo.scale-125.png differ diff --git a/logo/pwa/windows11/StoreLogo.scale-150.png b/logo/pwa/windows11/StoreLogo.scale-150.png new file mode 100644 index 0000000..b14b620 Binary files /dev/null and b/logo/pwa/windows11/StoreLogo.scale-150.png differ diff --git a/logo/pwa/windows11/StoreLogo.scale-200.png b/logo/pwa/windows11/StoreLogo.scale-200.png new file mode 100644 index 0000000..1a909bf Binary files /dev/null and b/logo/pwa/windows11/StoreLogo.scale-200.png differ diff --git a/logo/pwa/windows11/StoreLogo.scale-400.png b/logo/pwa/windows11/StoreLogo.scale-400.png new file mode 100644 index 0000000..92f78f3 Binary files /dev/null and b/logo/pwa/windows11/StoreLogo.scale-400.png differ diff --git a/logo/pwa/windows11/Wide310x150Logo.scale-100.png b/logo/pwa/windows11/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000..e420b19 Binary files /dev/null and b/logo/pwa/windows11/Wide310x150Logo.scale-100.png differ diff --git a/logo/pwa/windows11/Wide310x150Logo.scale-125.png b/logo/pwa/windows11/Wide310x150Logo.scale-125.png new file mode 100644 index 0000000..ab97268 Binary files /dev/null and b/logo/pwa/windows11/Wide310x150Logo.scale-125.png differ diff --git a/logo/pwa/windows11/Wide310x150Logo.scale-150.png b/logo/pwa/windows11/Wide310x150Logo.scale-150.png new file mode 100644 index 0000000..b78d271 Binary files /dev/null and b/logo/pwa/windows11/Wide310x150Logo.scale-150.png differ diff --git a/logo/pwa/windows11/Wide310x150Logo.scale-200.png b/logo/pwa/windows11/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..60ddbf2 Binary files /dev/null and b/logo/pwa/windows11/Wide310x150Logo.scale-200.png differ diff --git a/logo/pwa/windows11/Wide310x150Logo.scale-400.png b/logo/pwa/windows11/Wide310x150Logo.scale-400.png new file mode 100644 index 0000000..39eb6fb Binary files /dev/null and b/logo/pwa/windows11/Wide310x150Logo.scale-400.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f93c160 --- /dev/null +++ b/manifest.json @@ -0,0 +1,124 @@ +{ + "name": "SecureBit.chat - Enhanced Security Edition", + "short_name": "SecureBit", + "description": "P2P messenger with military-grade cryptography and Lightning Network payments", + "start_url": "/", + "display": "standalone", + "background_color": "#1a1a1a", + "theme_color": "#ff6b35", + "orientation": "portrait-primary", + "scope": "/", + "lang": "en", + "dir": "ltr", + "categories": ["communication", "security", "productivity"], + "iarc_rating_id": "", + "prefer_related_applications": false, + "icons": [ + { + "src": "logo/icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-128x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-152x152.png", + "sizes": "152x152", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-384x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "logo/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable any" + } + ], + "shortcuts": [ + { + "name": "Create Channel", + "short_name": "Create", + "description": "Create a new secure channel", + "url": "/?action=create", + "icons": [ + { + "src": "logo/icon-96x96.png", + "sizes": "96x96" + } + ] + }, + { + "name": "Join Channel", + "short_name": "Join", + "description": "Join an existing secure channel", + "url": "/?action=join", + "icons": [ + { + "src": "logo/icon-96x96.png", + "sizes": "96x96" + } + ] + } + ], + "screenshots": [ + { + "src": "screenshots/desktop-1.png", + "sizes": "1280x720", + "type": "image/png", + "form_factor": "wide", + "label": "SecureBit.chat main interface" + }, + { + "src": "screenshots/mobile-1.png", + "sizes": "414x896", + "type": "image/png", + "form_factor": "narrow", + "label": "SecureBit.chat mobile interface" + } + ], + "related_applications": [], + "protocol_handlers": [ + { + "protocol": "web+securebit", + "url": "/?invite=%s" + } + ], + "share_target": { + "action": "/share", + "method": "GET", + "params": { + "title": "title", + "text": "text", + "url": "url" + } + } +} \ No newline at end of file diff --git a/src/components/ui/Header.jsx b/src/components/ui/Header.jsx index d2ba892..6e7b78e 100644 --- a/src/components/ui/Header.jsx +++ b/src/components/ui/Header.jsx @@ -289,7 +289,7 @@ const EnhancedMinimalHeader = ({ React.createElement('p', { key: 'subtitle', className: 'text-xs sm:text-sm text-muted hidden sm:block' - }, 'End-to-end freedom. v4.1.1') + }, 'End-to-end freedom. v4.01.212') ]) ]), @@ -414,4 +414,4 @@ const EnhancedMinimalHeader = ({ window.EnhancedMinimalHeader = EnhancedMinimalHeader; -console.log('โœ… EnhancedMinimalHeader v4.1.1 loaded with real security status integration'); \ No newline at end of file +console.log('โœ… EnhancedMinimalHeader v4.01.212 loaded with real security status integration'); \ No newline at end of file diff --git a/src/pwa/install-prompt.js b/src/pwa/install-prompt.js new file mode 100644 index 0000000..dd9353d --- /dev/null +++ b/src/pwa/install-prompt.js @@ -0,0 +1,488 @@ +// PWA Install Prompt Manager for SecureBit.chat +// Enhanced Security Edition v4.01.212 + +class PWAInstallPrompt { + constructor() { + this.deferredPrompt = null; + this.isInstalled = false; + this.installButton = null; + this.installBanner = null; + this.dismissedCount = 0; + this.maxDismissals = 3; + + this.init(); + } + + init() { + console.log('๐Ÿ’ฟ PWA Install Prompt initializing...'); + + this.checkInstallationStatus(); + this.setupEventListeners(); + this.createInstallButton(); + this.loadInstallPreferences(); + + console.log('โœ… PWA Install Prompt initialized'); + } + + checkInstallationStatus() { + // Check if app is already installed + if (window.matchMedia('(display-mode: standalone)').matches || + window.navigator.standalone === true) { + this.isInstalled = true; + console.log('๐Ÿ“ฑ App is already installed as PWA'); + document.body.classList.add('pwa-installed'); + return true; + } + + // Check for iOS Safari specific installation + if (this.isIOSSafari()) { + this.isInstalled = window.navigator.standalone === true; + } + + document.body.classList.add(this.isInstalled ? 'pwa-installed' : 'pwa-browser'); + return this.isInstalled; + } + + setupEventListeners() { + // Capture the install prompt event + window.addEventListener('beforeinstallprompt', (event) => { + console.log('๐Ÿ’ฟ Install prompt event captured'); + event.preventDefault(); + this.deferredPrompt = event; + + if (!this.isInstalled && this.shouldShowPrompt()) { + this.showInstallOptions(); + } + }); + + // Handle successful installation + window.addEventListener('appinstalled', () => { + console.log('โœ… PWA installed successfully'); + this.isInstalled = true; + this.hideInstallPrompts(); + this.showInstallSuccess(); + this.saveInstallPreference('installed', true); + + // Update UI for installed state + document.body.classList.remove('pwa-browser'); + document.body.classList.add('pwa-installed'); + }); + + // Handle iOS installation detection + if (this.isIOSSafari()) { + window.addEventListener('visibilitychange', () => { + if (document.hidden) return; + + setTimeout(() => { + if (window.navigator.standalone && !this.isInstalled) { + this.isInstalled = true; + this.hideInstallPrompts(); + this.showInstallSuccess(); + } + }, 1000); + }); + } + } + + createInstallButton() { + // Create floating install button + this.installButton = document.createElement('button'); + this.installButton.id = 'pwa-install-button'; + this.installButton.className = 'hidden fixed bottom-6 right-6 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-6 py-3 rounded-full shadow-lg transition-all duration-300 z-50 flex items-center space-x-3 group'; + this.installButton.innerHTML = ` + + Install App +

+ `; + + this.installButton.addEventListener('click', () => { + this.handleInstallClick(); + }); + + document.body.appendChild(this.installButton); + } + + createInstallBanner() { + if (this.installBanner) return; + + this.installBanner = document.createElement('div'); + this.installBanner.id = 'pwa-install-banner'; + this.installBanner.className = 'pwa-install-banner fixed bottom-0 left-0 right-0 transform translate-y-full transition-transform duration-300 z-40'; + this.installBanner.innerHTML = ` +
+
+ +
+
+
Install SecureBit.chat
+
Get the native app experience with enhanced security
+
+
+
+ + +
+ `; + + // Handle banner actions + this.installBanner.addEventListener('click', (event) => { + const action = event.target.closest('[data-action]')?.dataset.action; + + if (action === 'install') { + this.handleInstallClick(); + } else if (action === 'dismiss') { + this.dismissInstallPrompt(); + } + }); + + document.body.appendChild(this.installBanner); + } + + showInstallOptions() { + if (this.isInstalled) return; + + // For mobile devices, show banner + if (this.isMobileDevice()) { + this.showInstallBanner(); + } else { + // For desktop, show floating button + this.showInstallButton(); + } + } + + showInstallButton() { + if (this.installButton && !this.isInstalled) { + this.installButton.classList.remove('hidden'); + + // Add entrance animation + setTimeout(() => { + this.installButton.style.transform = 'scale(1.1)'; + setTimeout(() => { + this.installButton.style.transform = 'scale(1)'; + }, 200); + }, 100); + + console.log('๐Ÿ’ฟ Install button shown'); + } + } + + showInstallBanner() { + if (!this.installBanner) { + this.createInstallBanner(); + } + + if (this.installBanner && !this.isInstalled) { + setTimeout(() => { + this.installBanner.classList.add('show'); + }, 1000); + + console.log('๐Ÿ’ฟ Install banner shown'); + } + } + + hideInstallPrompts() { + if (this.installButton) { + this.installButton.classList.add('hidden'); + } + + if (this.installBanner) { + this.installBanner.classList.remove('show'); + } + } + + async handleInstallClick() { + if (this.isIOSSafari()) { + this.showIOSInstructions(); + return; + } + + if (!this.deferredPrompt) { + console.warn('โš ๏ธ Install prompt not available'); + this.showFallbackInstructions(); + return; + } + + try { + console.log('๐Ÿ’ฟ Showing install prompt...'); + + // Show the install prompt + const result = await this.deferredPrompt.prompt(); + console.log('๐Ÿ’ฟ Install prompt result:', result.outcome); + + if (result.outcome === 'accepted') { + console.log('โœ… User accepted install prompt'); + this.hideInstallPrompts(); + this.saveInstallPreference('accepted', true); + } else { + console.log('โŒ User dismissed install prompt'); + this.handleInstallDismissal(); + } + + // Clear the deferred prompt + this.deferredPrompt = null; + + } catch (error) { + console.error('โŒ Install prompt failed:', error); + this.showFallbackInstructions(); + } + } + + showIOSInstructions() { + const modal = document.createElement('div'); + modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; + modal.innerHTML = ` +
+
+ +
+

Install on iOS

+
+
+
1
+ Tap the Share button +
+
+
2
+ Select "Add to Home Screen" +
+
+
3
+ Tap "Add" to install +
+
+ +
+ `; + + document.body.appendChild(modal); + } + + showFallbackInstructions() { + const modal = document.createElement('div'); + modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; + modal.innerHTML = ` +
+
+ +
+

Install SecureBit.chat

+

+ To install this app, look for the install option in your browser menu or address bar. + Different browsers have different install methods. +

+ +
+
+
Chrome/Edge
+
Look for install icon in address bar
+
+
+
Firefox
+
Add bookmark to home screen
+
+
+
Safari
+
Share โ†’ Add to Home Screen
+
+
+ + +
+ `; + + document.body.appendChild(modal); + } + + showInstallSuccess() { + const notification = document.createElement('div'); + notification.className = 'fixed top-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm transform translate-x-full transition-transform duration-300'; + notification.innerHTML = ` +
+
+ +
+
+
App Installed!
+
SecureBit.chat is now on your device
+
+
+ `; + + document.body.appendChild(notification); + + // Animate in + setTimeout(() => { + notification.classList.remove('translate-x-full'); + }, 100); + + // Auto-remove after 4 seconds + setTimeout(() => { + notification.classList.add('translate-x-full'); + setTimeout(() => notification.remove(), 300); + }, 4000); + } + + dismissInstallPrompt() { + this.dismissedCount++; + this.hideInstallPrompts(); + this.saveInstallPreference('dismissed', this.dismissedCount); + + console.log(`๐Ÿ’ฟ Install prompt dismissed (${this.dismissedCount}/${this.maxDismissals})`); + + // Show encouraging message on final dismissal + if (this.dismissedCount >= this.maxDismissals) { + this.showFinalDismissalMessage(); + } + } + + handleInstallDismissal() { + this.dismissedCount++; + this.saveInstallPreference('dismissed', this.dismissedCount); + + if (this.dismissedCount < this.maxDismissals) { + // Show reminder after some time + setTimeout(() => { + if (!this.isInstalled && this.shouldShowPrompt()) { + this.showInstallButton(); + } + }, 300000); // 5 minutes + } + } + + showFinalDismissalMessage() { + const notification = document.createElement('div'); + notification.className = 'fixed bottom-4 left-4 right-4 bg-blue-500/90 text-white p-4 rounded-lg shadow-lg z-50 backdrop-blur-sm'; + notification.innerHTML = ` +
+
+ +
+
+
Install Anytime
+
+ You can still install SecureBit.chat from your browser's menu for the best experience. +
+ +
+
+ `; + + document.body.appendChild(notification); + + setTimeout(() => { + if (notification.parentElement) { + notification.remove(); + } + }, 10000); + } + + shouldShowPrompt() { + const preferences = this.loadInstallPreferences(); + + // Don't show if already installed + if (this.isInstalled) return false; + + // Don't show if dismissed too many times + if (preferences.dismissed >= this.maxDismissals) return false; + + // Don't show if recently dismissed (less than 24 hours) + const lastDismissed = preferences.lastDismissed; + if (lastDismissed && Date.now() - lastDismissed < 24 * 60 * 60 * 1000) { + return false; + } + + return true; + } + + saveInstallPreference(action, value) { + const preferences = this.loadInstallPreferences(); + preferences[action] = value; + + if (action === 'dismissed') { + preferences.lastDismissed = Date.now(); + } + + try { + localStorage.setItem('pwa_install_prefs', JSON.stringify(preferences)); + } catch (error) { + console.warn('โš ๏ธ Could not save install preferences:', error); + } + } + + loadInstallPreferences() { + try { + const saved = localStorage.getItem('pwa_install_prefs'); + return saved ? JSON.parse(saved) : { dismissed: 0, installed: false }; + } catch (error) { + console.warn('โš ๏ธ Could not load install preferences:', error); + return { dismissed: 0, installed: false }; + } + } + + isMobileDevice() { + return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + } + + isIOSSafari() { + const userAgent = navigator.userAgent; + return /iPad|iPhone|iPod/.test(userAgent) && /Safari/.test(userAgent) && !/CriOS|FxiOS/.test(userAgent); + } + + // Public API methods + showInstallPrompt() { + if (this.deferredPrompt && !this.isInstalled) { + this.handleInstallClick(); + } else { + this.showFallbackInstructions(); + } + } + + hideInstallPrompt() { + this.hideInstallPrompts(); + } + + getInstallStatus() { + return { + isInstalled: this.isInstalled, + canPrompt: !!this.deferredPrompt, + dismissedCount: this.dismissedCount, + shouldShowPrompt: this.shouldShowPrompt() + }; + } + + resetDismissals() { + this.dismissedCount = 0; + this.saveInstallPreference('dismissed', 0); + console.log('๐Ÿ’ฟ Install dismissals reset'); + } +} + +// Export for module use +if (typeof module !== 'undefined' && module.exports) { + module.exports = PWAInstallPrompt; +} else { + window.PWAInstallPrompt = PWAInstallPrompt; +} + +// Auto-initialize +if (typeof window !== 'undefined') { + window.addEventListener('DOMContentLoaded', () => { + if (!window.pwaInstallPrompt) { + window.pwaInstallPrompt = new PWAInstallPrompt(); + } + }); +} \ No newline at end of file diff --git a/src/pwa/offline-manager.js b/src/pwa/offline-manager.js new file mode 100644 index 0000000..328f957 --- /dev/null +++ b/src/pwa/offline-manager.js @@ -0,0 +1,552 @@ +// PWA Offline Component for SecureBit.chat +// Handles offline functionality and user experience + +window.PWAOfflineManager = (() => { + 'use strict'; + + class PWAOfflineManager { + constructor() { + this.isOnline = navigator.onLine; + this.offlineQueue = []; + this.syncInProgress = false; + this.offlineIndicator = null; + + this.init(); + } + + init() { + console.log('๐Ÿ“ด PWA Offline Manager initializing...'); + + this.setupEventListeners(); + this.createOfflineIndicator(); + this.setupOfflineStorage(); + this.registerBackgroundSync(); + + // Show initial status + this.updateConnectionStatus(this.isOnline); + + console.log('โœ… PWA Offline Manager initialized'); + } + + setupEventListeners() { + window.addEventListener('online', () => { + console.log('๐ŸŒ Connection restored'); + this.isOnline = true; + this.updateConnectionStatus(true); + this.processOfflineQueue(); + }); + + window.addEventListener('offline', () => { + console.log('๐Ÿ“ด Connection lost'); + this.isOnline = false; + this.updateConnectionStatus(false); + this.showOfflineGuidance(); + }); + + // Monitor WebRTC connection status + document.addEventListener('peer-disconnect', () => { + if (!this.isOnline) { + this.handleOfflineDisconnect(); + } + }); + + // Monitor failed network requests + window.addEventListener('unhandledrejection', (event) => { + if (this.isNetworkError(event.reason)) { + this.handleNetworkFailure(event.reason); + } + }); + } + + createOfflineIndicator() { + this.offlineIndicator = document.createElement('div'); + this.offlineIndicator.id = 'pwa-connection-status'; + this.offlineIndicator.className = 'hidden fixed top-4 left-1/2 transform -translate-x-1/2 z-50 transition-all duration-300'; + document.body.appendChild(this.offlineIndicator); + } + + updateConnectionStatus(isOnline) { + if (!this.offlineIndicator) return; + + if (isOnline) { + this.offlineIndicator.innerHTML = ` +
+
+ ๐ŸŒ Back online +
+ `; + this.offlineIndicator.classList.remove('hidden'); + + // Hide after 3 seconds + setTimeout(() => { + this.offlineIndicator.classList.add('hidden'); + }, 3000); + } else { + this.offlineIndicator.innerHTML = ` +
+
+ ๐Ÿ“ด Offline mode +
+ `; + this.offlineIndicator.classList.remove('hidden'); + } + } + + showOfflineGuidance() { + const guidance = document.createElement('div'); + guidance.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; + guidance.innerHTML = ` +
+
+ +
+

Connection Lost

+

+ Your internet connection was lost. SecureBit.chat requires an active connection for secure P2P communication. +

+
+
+ + Your session and keys are preserved +
+
+ + No data is stored on servers +
+
+ + Connection will resume automatically +
+
+ +
+ `; + + document.body.appendChild(guidance); + + // Auto-remove after 10 seconds + setTimeout(() => { + if (guidance.parentElement) { + guidance.remove(); + } + }, 10000); + } + + setupOfflineStorage() { + // Initialize IndexedDB for offline data storage + this.initOfflineDB().catch(error => { + console.warn('โš ๏ธ Offline storage not available:', error); + }); + } + + async initOfflineDB() { + return new Promise((resolve, reject) => { + const request = indexedDB.open('SecureBitOffline', 1); + + request.onerror = () => reject(request.error); + request.onsuccess = () => { + this.offlineDB = request.result; + resolve(this.offlineDB); + }; + + request.onupgradeneeded = (event) => { + const db = event.target.result; + + // Store for offline queue + if (!db.objectStoreNames.contains('offlineQueue')) { + const queueStore = db.createObjectStore('offlineQueue', { + keyPath: 'id', + autoIncrement: true + }); + queueStore.createIndex('timestamp', 'timestamp', { unique: false }); + queueStore.createIndex('type', 'type', { unique: false }); + } + + // Store for session recovery + if (!db.objectStoreNames.contains('sessionData')) { + const sessionStore = db.createObjectStore('sessionData', { + keyPath: 'key' + }); + } + }; + }); + } + + registerBackgroundSync() { + if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) { + navigator.serviceWorker.ready.then(registration => { + console.log('๐Ÿ“ก Background sync registered'); + this.swRegistration = registration; + }); + } else { + console.warn('โš ๏ธ Background sync not supported'); + } + } + + async queueOfflineAction(action) { + if (!this.offlineDB) { + console.warn('โš ๏ธ Offline storage not available'); + return; + } + + const queueItem = { + ...action, + timestamp: Date.now(), + id: Date.now() + Math.random() + }; + + try { + const transaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite'); + const store = transaction.objectStore('offlineQueue'); + await store.add(queueItem); + + console.log('๐Ÿ“ค Action queued for when online:', action.type); + this.offlineQueue.push(queueItem); + + // Try to sync in background + if (this.swRegistration) { + await this.swRegistration.sync.register('retry-offline-actions'); + } + } catch (error) { + console.error('โŒ Failed to queue offline action:', error); + } + } + + async processOfflineQueue() { + if (this.syncInProgress || !this.isOnline) { + return; + } + + this.syncInProgress = true; + console.log('๐Ÿ”„ Processing offline queue...'); + + try { + if (this.offlineDB) { + const transaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite'); + const store = transaction.objectStore('offlineQueue'); + const allItems = await this.getAllFromStore(store); + + for (const item of allItems) { + try { + await this.processQueueItem(item); + await store.delete(item.id); + console.log('โœ… Processed offline action:', item.type); + } catch (error) { + console.error('โŒ Failed to process offline action:', error); + // Keep item in queue for retry + } + } + } + + // Process in-memory queue as fallback + const memoryQueue = [...this.offlineQueue]; + this.offlineQueue = []; + + for (const item of memoryQueue) { + try { + await this.processQueueItem(item); + } catch (error) { + console.error('โŒ Failed to process memory queue item:', error); + this.offlineQueue.push(item); // Re-queue on failure + } + } + + if (memoryQueue.length > 0) { + this.showSyncNotification(memoryQueue.length); + } + + } catch (error) { + console.error('โŒ Error processing offline queue:', error); + } finally { + this.syncInProgress = false; + } + } + + async getAllFromStore(store) { + return new Promise((resolve, reject) => { + const request = store.getAll(); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + + async processQueueItem(item) { + switch (item.type) { + case 'message': + return this.retryMessage(item.data); + case 'connection': + return this.retryConnection(item.data); + case 'payment_check': + return this.retryPaymentCheck(item.data); + default: + console.warn('Unknown queue item type:', item.type); + } + } + + async retryMessage(messageData) { + // Retry sending message when back online + if (window.webrtcManager && window.webrtcManager.isConnected()) { + return window.webrtcManager.sendMessage(messageData.content); + } + throw new Error('WebRTC not connected'); + } + + async retryConnection(connectionData) { + // Retry connection establishment + if (window.webrtcManager) { + return window.webrtcManager.retryConnection(); + } + throw new Error('WebRTC manager not available'); + } + + async retryPaymentCheck(paymentData) { + // Retry payment verification + if (window.sessionManager) { + return window.sessionManager.checkPaymentStatus(paymentData.checkingId); + } + throw new Error('Session manager not available'); + } + + showSyncNotification(count) { + const notification = document.createElement('div'); + notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm'; + notification.innerHTML = ` +
+ +
+
Sync Complete
+
${count} offline action(s) processed
+
+
+ `; + + document.body.appendChild(notification); + + // Auto-remove after 4 seconds + setTimeout(() => { + notification.remove(); + }, 4000); + } + + handleOfflineDisconnect() { + // Handle WebRTC disconnection while offline + console.log('๐Ÿ”Œ WebRTC disconnected while offline'); + + const reconnectBanner = document.createElement('div'); + reconnectBanner.className = 'fixed top-0 left-0 right-0 bg-yellow-500 text-black p-3 z-50 text-center'; + reconnectBanner.innerHTML = ` +
+ + Connection lost. Will attempt to reconnect when online. +
+ `; + + document.body.appendChild(reconnectBanner); + + setTimeout(() => { + if (reconnectBanner.parentElement) { + reconnectBanner.remove(); + } + }, 5000); + } + + handleNetworkFailure(error) { + console.log('๐ŸŒ Network failure detected:', error?.message); + + // Queue the failed action for retry + if (this.shouldQueueAction(error)) { + this.queueOfflineAction({ + type: 'network_retry', + data: { error: error?.message }, + timestamp: Date.now() + }); + } + } + + isNetworkError(error) { + if (!error) return false; + + const networkErrorMessages = [ + 'fetch', + 'network', + 'connection', + 'timeout', + 'offline', + 'ERR_NETWORK', + 'ERR_INTERNET_DISCONNECTED' + ]; + + const errorString = error.toString().toLowerCase(); + return networkErrorMessages.some(msg => errorString.includes(msg)); + } + + shouldQueueAction(error) { + // Determine if the action should be queued for retry + return this.isNetworkError(error) && !this.isOnline; + } + + async saveSessionForRecovery(sessionData) { + if (!this.offlineDB) return; + + try { + const transaction = this.offlineDB.transaction(['sessionData'], 'readwrite'); + const store = transaction.objectStore('sessionData'); + + await store.put({ + key: 'current_session', + data: sessionData, + timestamp: Date.now() + }); + + console.log('๐Ÿ’พ Session data saved for offline recovery'); + } catch (error) { + console.error('โŒ Failed to save session data:', error); + } + } + + async recoverSession() { + if (!this.offlineDB) return null; + + try { + const transaction = this.offlineDB.transaction(['sessionData'], 'readonly'); + const store = transaction.objectStore('sessionData'); + const result = await this.getFromStore(store, 'current_session'); + + if (result && Date.now() - result.timestamp < 24 * 60 * 60 * 1000) { // 24 hours + console.log('๐Ÿ”„ Session data recovered from offline storage'); + return result.data; + } + } catch (error) { + console.error('โŒ Failed to recover session data:', error); + } + + return null; + } + + async getFromStore(store, key) { + return new Promise((resolve, reject) => { + const request = store.get(key); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + + clearOfflineData() { + if (!this.offlineDB) return; + + try { + const transaction = this.offlineDB.transaction(['offlineQueue', 'sessionData'], 'readwrite'); + transaction.objectStore('offlineQueue').clear(); + transaction.objectStore('sessionData').clear(); + + this.offlineQueue = []; + console.log('๐Ÿ—‘๏ธ Offline data cleared'); + } catch (error) { + console.error('โŒ Failed to clear offline data:', error); + } + } + + getOfflineStatus() { + return { + isOnline: this.isOnline, + queueLength: this.offlineQueue.length, + syncInProgress: this.syncInProgress, + hasOfflineDB: !!this.offlineDB, + lastSync: this.lastSyncTime || null + }; + } + + // Public API methods + async addToOfflineQueue(type, data) { + return this.queueOfflineAction({ type, data }); + } + + forceSync() { + if (this.isOnline) { + return this.processOfflineQueue(); + } else { + console.warn('โš ๏ธ Cannot sync while offline'); + return Promise.resolve(); + } + } + + showOfflineHelp() { + const helpModal = document.createElement('div'); + helpModal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; + helpModal.innerHTML = ` +
+
+
+ +
+

Offline Mode Help

+
+ +
+
+

What works offline:

+
    +
  • โ€ข App interface and navigation
  • +
  • โ€ข Previously cached resources
  • +
  • โ€ข Session data recovery
  • +
  • โ€ข Offline message queuing
  • +
+
+ +
+

What needs internet:

+
    +
  • โ€ข P2P connections (WebRTC)
  • +
  • โ€ข Lightning payments
  • +
  • โ€ข Real-time messaging
  • +
  • โ€ข Session verification
  • +
+
+ +
+

+ + Your messages and actions will be automatically synced when you're back online. +

+
+
+ + +
+ `; + + document.body.appendChild(helpModal); + } + } + + // Initialize and return singleton + let instance = null; + + return { + getInstance() { + if (!instance) { + instance = new PWAOfflineManager(); + } + return instance; + }, + + init() { + return this.getInstance(); + } + }; +})(); + +// Auto-initialize when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + window.pwaOfflineManager = window.PWAOfflineManager.init(); + }); +} else { + window.pwaOfflineManager = window.PWAOfflineManager.init(); +} \ No newline at end of file diff --git a/src/pwa/pwa-manager.js b/src/pwa/pwa-manager.js new file mode 100644 index 0000000..df4c948 --- /dev/null +++ b/src/pwa/pwa-manager.js @@ -0,0 +1,1099 @@ +// PWA Offline Manager for SecureBit.chat +// Enhanced Security Edition v4.01.212 +// Handles offline functionality, data synchronization, and user experience + +class PWAOfflineManager { + constructor() { + this.isOnline = navigator.onLine; + this.offlineDB = null; + this.offlineQueue = []; + this.syncInProgress = false; + this.lastSyncTime = null; + this.offlineIndicator = null; + this.reconnectAttempts = 0; + this.maxReconnectAttempts = 5; + this.reconnectInterval = null; + + // Offline storage configuration + this.dbConfig = { + name: 'SecureBitOffline', + version: 2, + stores: { + offlineQueue: { + keyPath: 'id', + autoIncrement: true, + indexes: { + timestamp: { unique: false }, + type: { unique: false }, + priority: { unique: false } + } + }, + sessionData: { + keyPath: 'key' + }, + messageQueue: { + keyPath: 'id', + autoIncrement: true, + indexes: { + timestamp: { unique: false }, + channelId: { unique: false } + } + }, + appState: { + keyPath: 'component' + } + } + }; + + this.init(); + } + + async init() { + console.log('๐Ÿ“ด PWA Offline Manager initializing...'); + + try { + // Initialize offline database + await this.initOfflineDB(); + + // Setup event listeners + this.setupEventListeners(); + + // Create offline indicator + this.createOfflineIndicator(); + + // Register background sync + this.registerBackgroundSync(); + + // Setup periodic cleanup + this.setupPeriodicCleanup(); + + // Show initial connection status + this.updateConnectionStatus(this.isOnline); + + // Try to process any pending queue items + if (this.isOnline) { + await this.processOfflineQueue(); + } + + console.log('โœ… PWA Offline Manager initialized'); + + } catch (error) { + console.error('โŒ Offline Manager initialization failed:', error); + this.handleInitializationError(error); + } + } + + async initOfflineDB() { + if (!('indexedDB' in window)) { + throw new Error('IndexedDB not supported'); + } + + return new Promise((resolve, reject) => { + const request = indexedDB.open(this.dbConfig.name, this.dbConfig.version); + + request.onerror = () => { + reject(new Error('Failed to open offline database')); + }; + + request.onsuccess = () => { + this.offlineDB = request.result; + console.log('๐Ÿ’พ Offline database opened successfully'); + resolve(this.offlineDB); + }; + + request.onupgradeneeded = (event) => { + const db = event.target.result; + + // Create object stores + Object.entries(this.dbConfig.stores).forEach(([storeName, config]) => { + if (!db.objectStoreNames.contains(storeName)) { + console.log(`๐Ÿ“ฆ Creating object store: ${storeName}`); + + const store = db.createObjectStore(storeName, { + keyPath: config.keyPath, + autoIncrement: config.autoIncrement || false + }); + + // Create indexes + if (config.indexes) { + Object.entries(config.indexes).forEach(([indexName, indexConfig]) => { + store.createIndex(indexName, indexName, indexConfig); + }); + } + } + }); + }; + }); + } + + setupEventListeners() { + // Network status changes + window.addEventListener('online', () => { + console.log('๐ŸŒ Connection restored'); + this.isOnline = true; + this.reconnectAttempts = 0; + this.updateConnectionStatus(true); + this.handleConnectionRestored(); + }); + + window.addEventListener('offline', () => { + console.log('๐Ÿ“ด Connection lost'); + this.isOnline = false; + this.updateConnectionStatus(false); + this.handleConnectionLost(); + }); + + // App visibility changes + document.addEventListener('visibilitychange', () => { + if (!document.hidden && this.isOnline) { + // Try to sync when app becomes visible + setTimeout(() => this.processOfflineQueue(), 1000); + } + }); + + // Listen for WebRTC connection events + document.addEventListener('peer-disconnect', (event) => { + if (!this.isOnline) { + this.handleOfflineDisconnection(event.detail); + } + }); + + // Listen for failed network requests + window.addEventListener('unhandledrejection', (event) => { + if (this.isNetworkError(event.reason)) { + this.handleNetworkFailure(event.reason); + } + }); + + // Listen for beforeunload to save state + window.addEventListener('beforeunload', () => { + this.saveApplicationState(); + }); + } + + createOfflineIndicator() { + this.offlineIndicator = document.createElement('div'); + this.offlineIndicator.id = 'pwa-connection-status'; + this.offlineIndicator.className = 'hidden fixed top-4 left-1/2 transform -translate-x-1/2 z-50 transition-all duration-300'; + document.body.appendChild(this.offlineIndicator); + } + + updateConnectionStatus(isOnline) { + if (!this.offlineIndicator) return; + + if (isOnline) { + this.offlineIndicator.innerHTML = ` +
+
+ ๐ŸŒ Back online +
+ `; + this.offlineIndicator.classList.remove('hidden'); + + // Hide after 3 seconds + setTimeout(() => { + this.offlineIndicator.classList.add('hidden'); + }, 3000); + } else { + this.offlineIndicator.innerHTML = ` +
+
+ ๐Ÿ“ด Offline mode + +
+ `; + this.offlineIndicator.classList.remove('hidden'); + } + } + + async handleConnectionRestored() { + console.log('๐Ÿ”„ Handling connection restoration...'); + + try { + // Process offline queue + await this.processOfflineQueue(); + + // Restore WebRTC connections if needed + await this.attemptWebRTCReconnection(); + + // Show success notification + this.showReconnectionSuccess(); + + } catch (error) { + console.error('โŒ Connection restoration failed:', error); + this.showReconnectionError(error); + } + } + + handleConnectionLost() { + console.log('๐Ÿ“ด Handling connection loss...'); + + // Show offline guidance + this.showOfflineGuidance(); + + // Save current application state + this.saveApplicationState(); + + // Start reconnection attempts + this.startReconnectionAttempts(); + } + + showOfflineGuidance() { + // Don't show if already shown recently + const lastShown = localStorage.getItem('offline_guidance_shown'); + if (lastShown && Date.now() - parseInt(lastShown) < 60000) { // 1 minute + return; + } + + const guidance = document.createElement('div'); + guidance.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm'; + guidance.innerHTML = ` +
+
+ +
+

Connection Lost

+

+ SecureBit.chat is now in offline mode. Some features are limited, but your data is safe. +

+ +
+
+
+ +
+ Your session and keys are preserved +
+
+
+ +
+ No data is stored on servers +
+
+
+ +
+ Messages will sync when online +
+
+ +
+ + +
+
+ `; + + document.body.appendChild(guidance); + + // Save that we showed the guidance + localStorage.setItem('offline_guidance_shown', Date.now().toString()); + + // Auto-remove after 15 seconds + setTimeout(() => { + if (guidance.parentElement) { + guidance.remove(); + } + }, 15000); + } + + startReconnectionAttempts() { + if (this.reconnectInterval) { + clearInterval(this.reconnectInterval); + } + + this.reconnectInterval = setInterval(() => { + if (this.isOnline) { + clearInterval(this.reconnectInterval); + this.reconnectInterval = null; + return; + } + + this.reconnectAttempts++; + console.log(`๐Ÿ”„ Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`); + + // Try to detect if we're actually back online + this.checkOnlineStatus(); + + if (this.reconnectAttempts >= this.maxReconnectAttempts) { + clearInterval(this.reconnectInterval); + this.reconnectInterval = null; + console.log('โŒ Max reconnection attempts reached'); + } + }, 10000); // Try every 10 seconds + } + + async checkOnlineStatus() { + try { + // Try to fetch a small resource to check connectivity + const response = await fetch('/favicon.ico', { + method: 'HEAD', + cache: 'no-cache', + signal: AbortSignal.timeout(5000) + }); + + if (response.ok && !this.isOnline) { + // We're actually online but navigator.onLine is wrong + console.log('๐ŸŒ Detected online status, updating...'); + this.isOnline = true; + this.handleConnectionRestored(); + } + } catch (error) { + // Still offline + console.log('๐Ÿ“ด Still offline'); + } + } + + async queueOfflineAction(action) { + if (!this.offlineDB) { + console.warn('โš ๏ธ Offline database not available'); + this.offlineQueue.push(action); + return; + } + + const queueItem = { + ...action, + id: Date.now() + Math.random(), + timestamp: Date.now(), + priority: action.priority || 1, + retryCount: 0, + maxRetries: action.maxRetries || 3 + }; + + try { + const transaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite'); + const store = transaction.objectStore('offlineQueue'); + await this.promisifyRequest(store.add(queueItem)); + + console.log('๐Ÿ“ค Action queued for offline sync:', action.type); + this.offlineQueue.push(queueItem); + + // Try to register background sync + if (this.registration) { + await this.registration.sync.register('offline-sync'); + } + } catch (error) { + console.error('โŒ Failed to queue offline action:', error); + // Fallback to memory queue + this.offlineQueue.push(queueItem); + } + } + + async processOfflineQueue() { + if (this.syncInProgress || !this.isOnline) { + return; + } + + this.syncInProgress = true; + console.log('๐Ÿ”„ Processing offline queue...'); + + let processedCount = 0; + let errorCount = 0; + + try { + // Process database queue + if (this.offlineDB) { + const transaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite'); + const store = transaction.objectStore('offlineQueue'); + const allItems = await this.promisifyRequest(store.getAll()); + + // Sort by priority and timestamp + allItems.sort((a, b) => { + if (a.priority !== b.priority) { + return b.priority - a.priority; // Higher priority first + } + return a.timestamp - b.timestamp; // Older first + }); + + for (const item of allItems) { + try { + await this.processQueueItem(item); + await this.promisifyRequest(store.delete(item.id)); + processedCount++; + console.log('โœ… Processed offline action:', item.type); + } catch (error) { + console.error('โŒ Failed to process offline action:', error); + errorCount++; + + // Increment retry count + item.retryCount = (item.retryCount || 0) + 1; + + if (item.retryCount >= item.maxRetries) { + // Max retries reached, remove from queue + await this.promisifyRequest(store.delete(item.id)); + console.log('โŒ Max retries reached for action:', item.type); + } else { + // Update retry count in database + await this.promisifyRequest(store.put(item)); + } + } + } + } + + // Process in-memory queue as fallback + const memoryQueue = [...this.offlineQueue]; + this.offlineQueue = []; + + for (const item of memoryQueue) { + try { + await this.processQueueItem(item); + processedCount++; + } catch (error) { + console.error('โŒ Failed to process memory queue item:', error); + errorCount++; + + item.retryCount = (item.retryCount || 0) + 1; + if (item.retryCount < item.maxRetries) { + this.offlineQueue.push(item); // Re-queue for retry + } + } + } + + this.lastSyncTime = Date.now(); + + if (processedCount > 0 || errorCount > 0) { + this.showSyncNotification(processedCount, errorCount); + } + + } catch (error) { + console.error('โŒ Error processing offline queue:', error); + } finally { + this.syncInProgress = false; + } + } + + async processQueueItem(item) { + switch (item.type) { + case 'message': + return this.retryMessage(item.data); + case 'connection': + return this.retryConnection(item.data); + case 'payment_check': + return this.retryPaymentCheck(item.data); + case 'session_verification': + return this.retrySessionVerification(item.data); + case 'key_exchange': + return this.retryKeyExchange(item.data); + default: + console.warn('Unknown queue item type:', item.type); + throw new Error(`Unknown queue item type: ${item.type}`); + } + } + + async retryMessage(messageData) { + // Retry sending message when back online + if (window.webrtcManager && window.webrtcManager.isConnected()) { + return window.webrtcManager.sendMessage(messageData.content); + } + throw new Error('WebRTC not connected'); + } + + async retryConnection(connectionData) { + // Retry connection establishment + if (window.webrtcManager) { + return window.webrtcManager.retryConnection(); + } + throw new Error('WebRTC manager not available'); + } + + async retryPaymentCheck(paymentData) { + // Retry payment verification + if (window.sessionManager) { + return window.sessionManager.checkPaymentStatus(paymentData.checkingId); + } + throw new Error('Session manager not available'); + } + + async retrySessionVerification(sessionData) { + // Retry session verification + if (window.sessionManager) { + return window.sessionManager.verifyPayment(sessionData.preimage, sessionData.paymentHash); + } + throw new Error('Session manager not available'); + } + + async retryKeyExchange(keyData) { + // Retry key exchange + if (window.webrtcManager) { + return window.webrtcManager.handleKeyExchange(keyData); + } + throw new Error('WebRTC manager not available'); + } + + showSyncNotification(successCount, errorCount) { + const notification = document.createElement('div'); + notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm transform translate-x-full transition-transform duration-300'; + + let message = ''; + if (successCount > 0 && errorCount === 0) { + message = `โœ… Synced ${successCount} offline action${successCount > 1 ? 's' : ''}`; + } else if (successCount > 0 && errorCount > 0) { + message = `โš ๏ธ Synced ${successCount}, ${errorCount} failed`; + } else if (errorCount > 0) { + message = `โŒ ${errorCount} sync error${errorCount > 1 ? 's' : ''}`; + } + + notification.innerHTML = ` +
+ +
+
Sync Complete
+
${message}
+
+
+ `; + + document.body.appendChild(notification); + + // Animate in + setTimeout(() => { + notification.classList.remove('translate-x-full'); + }, 100); + + // Auto-remove after 4 seconds + setTimeout(() => { + notification.classList.add('translate-x-full'); + setTimeout(() => notification.remove(), 300); + }, 4000); + } + + async attemptWebRTCReconnection() { + if (!window.webrtcManager) return; + + try { + // Check if we had an active connection before going offline + const savedConnectionState = await this.getStoredData('sessionData', 'connection_state'); + + if (savedConnectionState && savedConnectionState.wasConnected) { + console.log('๐Ÿ”„ Attempting WebRTC reconnection...'); + + // Show reconnection indicator + this.showReconnectionIndicator(); + + // Attempt to restore connection + // This would depend on your specific WebRTC implementation + if (window.webrtcManager.attemptReconnection) { + await window.webrtcManager.attemptReconnection(savedConnectionState.data); + } + } + } catch (error) { + console.error('โŒ WebRTC reconnection failed:', error); + } + } + + showReconnectionIndicator() { + const indicator = document.createElement('div'); + indicator.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-blue-500/90 text-white px-6 py-4 rounded-lg backdrop-blur-sm z-50'; + indicator.innerHTML = ` +
+ +
+
Reconnecting...
+
Restoring secure connection
+
+
+ `; + + document.body.appendChild(indicator); + + // Remove after 5 seconds or when connection is restored + setTimeout(() => { + if (indicator.parentElement) { + indicator.remove(); + } + }, 5000); + } + + showReconnectionSuccess() { + const notification = document.createElement('div'); + notification.className = 'fixed top-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm'; + notification.innerHTML = ` +
+ +
+
Reconnected!
+
All services restored
+
+
+ `; + + document.body.appendChild(notification); + + setTimeout(() => notification.remove(), 3000); + } + + showReconnectionError(error) { + const notification = document.createElement('div'); + notification.className = 'fixed top-4 right-4 bg-yellow-500 text-black p-4 rounded-lg shadow-lg z-50 max-w-sm'; + notification.innerHTML = ` +
+ +
+
Reconnection Issue
+
Some features may need manual restart
+
+
+ `; + + document.body.appendChild(notification); + + setTimeout(() => notification.remove(), 5000); + } + + async saveApplicationState() { + if (!this.offlineDB) return; + + try { + const appState = { + component: 'app_state', + timestamp: Date.now(), + url: window.location.href, + + // Save WebRTC connection state + webrtc: window.webrtcManager ? { + isConnected: window.webrtcManager.isConnected(), + connectionState: window.webrtcManager.getConnectionInfo(), + } : null, + + // Save session state + session: window.sessionManager ? { + hasActiveSession: window.sessionManager.hasActiveSession(), + sessionInfo: window.sessionManager.getSessionInfo(), + } : null, + + // Save UI state + ui: { + currentTab: document.querySelector('.active')?.id, + scrollPosition: window.pageYOffset, + } + }; + + const transaction = this.offlineDB.transaction(['appState'], 'readwrite'); + const store = transaction.objectStore('appState'); + await this.promisifyRequest(store.put(appState)); + + console.log('๐Ÿ’พ Application state saved for offline recovery'); + } catch (error) { + console.error('โŒ Failed to save application state:', error); + } + } + + async restoreApplicationState() { + if (!this.offlineDB) return null; + + try { + const savedState = await this.getStoredData('appState', 'app_state'); + + if (savedState && Date.now() - savedState.timestamp < 24 * 60 * 60 * 1000) { // 24 hours + console.log('๐Ÿ”„ Restoring application state from offline storage'); + return savedState; + } + } catch (error) { + console.error('โŒ Failed to restore application state:', error); + } + + return null; + } + + async storeData(storeName, data) { + if (!this.offlineDB) { + throw new Error('Offline database not available'); + } + + const transaction = this.offlineDB.transaction([storeName], 'readwrite'); + const store = transaction.objectStore(storeName); + return this.promisifyRequest(store.put(data)); + } + + async getStoredData(storeName, key) { + if (!this.offlineDB) { + return null; + } + + try { + const transaction = this.offlineDB.transaction([storeName], 'readonly'); + const store = transaction.objectStore(storeName); + const result = await this.promisifyRequest(store.get(key)); + return result; + } catch (error) { + console.error(`โŒ Failed to get stored data from ${storeName}:`, error); + return null; + } + } + + async clearStoredData(storeName, key = null) { + if (!this.offlineDB) return; + + try { + const transaction = this.offlineDB.transaction([storeName], 'readwrite'); + const store = transaction.objectStore(storeName); + + if (key) { + await this.promisifyRequest(store.delete(key)); + } else { + await this.promisifyRequest(store.clear()); + } + + console.log(`๐Ÿ—‘๏ธ Cleared stored data from ${storeName}`); + } catch (error) { + console.error(`โŒ Failed to clear stored data from ${storeName}:`, error); + } + } + + registerBackgroundSync() { + if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) { + navigator.serviceWorker.ready.then(registration => { + this.registration = registration; + console.log('๐Ÿ“ก Background sync registered'); + }); + } else { + console.warn('โš ๏ธ Background sync not supported'); + } + } + + setupPeriodicCleanup() { + // Clean up old data every hour + setInterval(() => { + this.cleanupOldData(); + }, 60 * 60 * 1000); + } + + async cleanupOldData() { + if (!this.offlineDB) return; + + const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days + const cutoffTime = Date.now() - maxAge; + + try { + const transaction = this.offlineDB.transaction(['offlineQueue', 'messageQueue'], 'readwrite'); + + // Clean offline queue + const queueStore = transaction.objectStore('offlineQueue'); + const queueIndex = queueStore.index('timestamp'); + const queueRange = IDBKeyRange.upperBound(cutoffTime); + + const queueRequest = queueIndex.openCursor(queueRange); + queueRequest.onsuccess = (event) => { + const cursor = event.target.result; + if (cursor) { + cursor.delete(); + cursor.continue(); + } + }; + + // Clean message queue + const messageStore = transaction.objectStore('messageQueue'); + const messageIndex = messageStore.index('timestamp'); + const messageRange = IDBKeyRange.upperBound(cutoffTime); + + const messageRequest = messageIndex.openCursor(messageRange); + messageRequest.onsuccess = (event) => { + const cursor = event.target.result; + if (cursor) { + cursor.delete(); + cursor.continue(); + } + }; + + console.log('๐Ÿงน Old offline data cleaned up'); + } catch (error) { + console.error('โŒ Failed to cleanup old data:', error); + } + } + + handleOfflineDisconnection(details) { + console.log('๐Ÿ”Œ WebRTC disconnected while offline:', details); + + // Save connection state for recovery + this.storeData('sessionData', { + key: 'connection_state', + wasConnected: true, + disconnectReason: details.reason, + timestamp: Date.now(), + data: details + }); + + // Show user feedback + this.showOfflineDisconnectionNotice(); + } + + showOfflineDisconnectionNotice() { + const notice = document.createElement('div'); + notice.className = 'fixed bottom-4 left-4 right-4 bg-yellow-500/90 text-black p-4 rounded-lg backdrop-blur-sm z-50'; + notice.innerHTML = ` +
+ +
+
Connection Interrupted
+
+ Your secure connection was lost due to network issues. + It will be restored automatically when you're back online. +
+
+ +
+ `; + + document.body.appendChild(notice); + + setTimeout(() => { + if (notice.parentElement) { + notice.remove(); + } + }, 8000); + } + + handleNetworkFailure(error) { + console.log('๐ŸŒ Network failure detected:', error?.message); + + // Queue the failed action for retry if appropriate + if (this.shouldQueueFailedRequest(error)) { + this.queueOfflineAction({ + type: 'network_retry', + data: { error: error?.message }, + priority: 1, + maxRetries: 2 + }); + } + } + + shouldQueueFailedRequest(error) { + if (!error) return false; + + const queueableErrors = [ + 'fetch', + 'network', + 'connection', + 'timeout', + 'offline', + 'ERR_NETWORK', + 'ERR_INTERNET_DISCONNECTED' + ]; + + const errorString = error.toString().toLowerCase(); + return queueableErrors.some(err => errorString.includes(err)) && !this.isOnline; + } + + isNetworkError(error) { + if (!error) return false; + + const networkErrorPatterns = [ + /fetch/i, + /network/i, + /connection/i, + /timeout/i, + /offline/i, + /ERR_NETWORK/i, + /ERR_INTERNET_DISCONNECTED/i + ]; + + const errorString = error.toString(); + return networkErrorPatterns.some(pattern => pattern.test(errorString)); + } + + handleInitializationError(error) { + console.error('๐Ÿšจ Offline manager initialization error:', error); + + // Show fallback UI + const fallback = document.createElement('div'); + fallback.className = 'fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm'; + fallback.innerHTML = ` +
+ +
+
Offline Mode Unavailable
+
+ Some offline features may not work properly. + Please ensure you have a stable internet connection. +
+
+
+ `; + + document.body.appendChild(fallback); + + setTimeout(() => fallback.remove(), 8000); + } + + showOfflineHelp() { + const helpModal = document.createElement('div'); + helpModal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm'; + helpModal.innerHTML = ` +
+
+
+ +
+

Offline Mode Guide

+
+ +
+
+

+ + What works offline: +

+
    +
  • โ€ข App interface and navigation
  • +
  • โ€ข Previously cached resources
  • +
  • โ€ข Session data and keys (preserved in memory)
  • +
  • โ€ข Message queuing for later delivery
  • +
  • โ€ข Basic cryptographic operations
  • +
+
+ +
+

+ + What requires internet: +

+
    +
  • โ€ข P2P connections (WebRTC)
  • +
  • โ€ข Lightning Network payments
  • +
  • โ€ข Real-time messaging
  • +
  • โ€ข Session verification
  • +
  • โ€ข Key exchange with new peers
  • +
+
+ +
+

+ + Automatic sync: +

+

+ When you're back online, all queued messages and actions + will be automatically synchronized. No data is lost. +

+
+ +
+

+ + Security Notice +

+

+ Your encryption keys and session data remain secure even offline. + SecureBit.chat never stores sensitive information on servers. +

+
+
+ + +
+ `; + + document.body.appendChild(helpModal); + } + + promisifyRequest(request) { + return new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + + // Public API methods + async addToQueue(type, data, priority = 1) { + return this.queueOfflineAction({ type, data, priority }); + } + + async forceSync() { + if (this.isOnline) { + return this.processOfflineQueue(); + } else { + console.warn('โš ๏ธ Cannot sync while offline'); + return Promise.resolve(); + } + } + + getStatus() { + return { + isOnline: this.isOnline, + queueLength: this.offlineQueue.length, + syncInProgress: this.syncInProgress, + hasOfflineDB: !!this.offlineDB, + lastSyncTime: this.lastSyncTime, + reconnectAttempts: this.reconnectAttempts + }; + } + + clearOfflineData() { + return Promise.all([ + this.clearStoredData('offlineQueue'), + this.clearStoredData('messageQueue'), + this.clearStoredData('sessionData'), + this.clearStoredData('appState') + ]).then(() => { + this.offlineQueue = []; + console.log('๐Ÿ—‘๏ธ All offline data cleared'); + }); + } + + // Cleanup method + destroy() { + if (this.reconnectInterval) { + clearInterval(this.reconnectInterval); + } + + if (this.offlineDB) { + this.offlineDB.close(); + } + + console.log('๐Ÿงน Offline Manager destroyed'); + } +} + +// Singleton pattern +let instance = null; + +const PWAOfflineManager = { + getInstance() { + if (!instance) { + instance = new PWAOfflineManager(); + } + return instance; + }, + + init() { + return this.getInstance(); + } +}; + +// Export for module use +if (typeof module !== 'undefined' && module.exports) { + module.exports = PWAOfflineManager; +} else { + window.PWAOfflineManager = PWAOfflineManager; +} + +// Auto-initialize when DOM is ready +if (typeof window !== 'undefined') { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + if (!window.pwaOfflineManager) { + window.pwaOfflineManager = PWAOfflineManager.init(); + } + }); + } else { + if (!window.pwaOfflineManager) { + window.pwaOfflineManager = PWAOfflineManager.init(); + } + } +} \ No newline at end of file diff --git a/src/styles/pwa.css b/src/styles/pwa.css new file mode 100644 index 0000000..52ab6a2 --- /dev/null +++ b/src/styles/pwa.css @@ -0,0 +1,367 @@ +/* PWA Specific Styles for SecureBit.chat */ + +/* PWA Install Button */ +#pwa-install-button { + backdrop-filter: blur(10px); + box-shadow: 0 8px 32px rgba(255, 107, 53, 0.3); + border: 1px solid rgba(255, 107, 53, 0.2); + animation: pulse-install 2s infinite; +} + +#pwa-install-button:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(255, 107, 53, 0.4); +} + +@keyframes pulse-install { + 0%, 100% { + box-shadow: 0 8px 32px rgba(255, 107, 53, 0.3); + } + 50% { + box-shadow: 0 8px 32px rgba(255, 107, 53, 0.5); + } +} + +/* PWA Update Banner */ +#pwa-update-banner { + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + animation: slideDown 0.3s ease-out; +} + +@keyframes slideDown { + from { + transform: translateY(-100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* PWA Notifications */ +.pwa-notification { + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +/* PWA Specific Layout Adjustments */ +.pwa-installed { + /* Adjustments for when app is installed as PWA */ +} + +.pwa-installed .header { + /* Adjust header for PWA mode */ + padding-top: env(safe-area-inset-top); +} + +.pwa-browser { + /* Adjustments for browser mode */ +} + +/* iOS PWA Status Bar */ +@supports (-webkit-touch-callout: none) { + .pwa-installed { + padding-top: env(safe-area-inset-top); + } + + .pwa-installed .header { + padding-top: calc(env(safe-area-inset-top) + 1rem); + } +} + +/* PWA Splash Screen Styles */ +.pwa-splash { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 9999; + transition: opacity 0.5s ease-out, visibility 0.5s ease-out; +} + +.pwa-splash.hidden { + opacity: 0; + visibility: hidden; +} + +.pwa-splash .logo { + width: 120px; + height: 120px; + background: linear-gradient(135deg, #ff6b35, #f7941d); + border-radius: 24px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 24px; + animation: pulse-logo 2s infinite; +} + +.pwa-splash .logo i { + font-size: 48px; + color: white; +} + +@keyframes pulse-logo { + 0%, 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(255, 107, 53, 0.4); + } + 50% { + transform: scale(1.05); + box-shadow: 0 0 0 20px rgba(255, 107, 53, 0); + } +} + +.pwa-splash .title { + color: #ff6b35; + font-size: 24px; + font-weight: 600; + margin-bottom: 8px; +} + +.pwa-splash .subtitle { + color: #9ca3af; + font-size: 14px; + margin-bottom: 32px; +} + +.pwa-splash .loading { + width: 200px; + height: 4px; + background: rgba(255, 107, 53, 0.2); + border-radius: 2px; + overflow: hidden; +} + +.pwa-splash .loading::after { + content: ''; + display: block; + width: 50%; + height: 100%; + background: linear-gradient(90deg, #ff6b35, #f7941d); + border-radius: 2px; + animation: loading-bar 1.5s ease-in-out infinite; +} + +@keyframes loading-bar { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(300%); } +} + +/* PWA Offline Indicator */ +.pwa-offline-indicator { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: rgba(239, 68, 68, 0.9); + color: white; + padding: 8px 16px; + border-radius: 20px; + font-size: 14px; + font-weight: 500; + backdrop-filter: blur(10px); + border: 1px solid rgba(239, 68, 68, 0.3); + animation: fadeInUp 0.3s ease-out; + z-index: 1000; +} + +.pwa-online-indicator { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: rgba(34, 197, 94, 0.9); + color: white; + padding: 8px 16px; + border-radius: 20px; + font-size: 14px; + font-weight: 500; + backdrop-filter: blur(10px); + border: 1px solid rgba(34, 197, 94, 0.3); + animation: fadeInUp 0.3s ease-out; + z-index: 1000; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translate(-50%, 20px); + } + to { + opacity: 1; + transform: translate(-50%, 0); + } +} + +/* PWA Safe Area Adjustments */ +@supports (padding: max(0px)) { + .pwa-safe-area { + padding-left: max(16px, env(safe-area-inset-left)); + padding-right: max(16px, env(safe-area-inset-right)); + padding-bottom: max(16px, env(safe-area-inset-bottom)); + } + + .pwa-safe-area-top { + padding-top: max(16px, env(safe-area-inset-top)); + } +} + +/* PWA Install Promotion Banner */ +.pwa-install-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(135deg, #ff6b35, #f7941d); + color: white; + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + transform: translateY(100%); + transition: transform 0.3s ease-out; + z-index: 1000; +} + +.pwa-install-banner.show { + transform: translateY(0); +} + +.pwa-install-banner .content { + flex: 1; + display: flex; + align-items: center; +} + +.pwa-install-banner .icon { + width: 48px; + height: 48px; + background: rgba(255, 255, 255, 0.2); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 16px; +} + +.pwa-install-banner .text { + flex: 1; +} + +.pwa-install-banner .title { + font-weight: 600; + font-size: 16px; + margin-bottom: 4px; +} + +.pwa-install-banner .subtitle { + font-size: 14px; + opacity: 0.9; +} + +.pwa-install-banner .actions { + display: flex; + gap: 12px; +} + +.pwa-install-banner button { + padding: 8px 16px; + border-radius: 8px; + font-weight: 500; + font-size: 14px; + transition: all 0.2s ease; + border: none; + cursor: pointer; +} + +.pwa-install-banner .install-btn { + background: white; + color: #ff6b35; +} + +.pwa-install-banner .install-btn:hover { + transform: scale(1.05); +} + +.pwa-install-banner .dismiss-btn { + background: transparent; + color: white; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.pwa-install-banner .dismiss-btn:hover { + background: rgba(255, 255, 255, 0.1); +} + +/* PWA Responsive Adjustments */ +@media (max-width: 768px) { + .pwa-install-banner { + padding: 12px; + } + + .pwa-install-banner .actions { + flex-direction: column; + gap: 8px; + } + + .pwa-install-banner button { + font-size: 12px; + padding: 6px 12px; + } +} + +/* PWA Dark Mode Support */ +@media (prefers-color-scheme: dark) { + .pwa-notification { + border-color: rgba(255, 255, 255, 0.1); + } +} + +/* PWA Animation Classes */ +.pwa-fade-in { + animation: fadeIn 0.3s ease-out; +} + +.pwa-slide-up { + animation: slideUp 0.3s ease-out; +} + +.pwa-scale-in { + animation: scaleIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} \ No newline at end of file diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..1ecf01e --- /dev/null +++ b/sw.js @@ -0,0 +1,355 @@ +// SecureBit.chat Service Worker +// Enhanced Security Edition v4.01.212 + +const CACHE_NAME = 'securebit-v4.0.3'; +const STATIC_CACHE = 'securebit-static-v4.0.3'; +const DYNAMIC_CACHE = 'securebit-dynamic-v4.0.3'; + +// Files to cache for offline functionality (excluding external CDNs that may have CORS issues) +const STATIC_ASSETS = [ + '/', + '/index.html', + '/manifest.json', + '/src/crypto/EnhancedSecureCryptoUtils.js', + '/src/network/EnhancedSecureWebRTCManager.js', + '/src/session/PayPerSessionManager.js', + '/src/components/ui/SessionTimer.jsx', + '/src/components/ui/Header.jsx', + '/src/components/ui/PasswordModal.jsx', + '/src/components/ui/SessionTypeSelector.jsx', + '/src/components/ui/LightningPayment.jsx', + '/src/components/ui/PaymentModal.jsx', + '/src/components/ui/DownloadApps.jsx', + '/src/styles/main.css', + '/src/styles/animations.css', + '/src/styles/components.css', + '/src/styles/pwa.css', + '/logo/favicon.ico' +]; + +// Sensitive files that should never be cached +const SENSITIVE_PATTERNS = [ + /\/api\//, + /preimage/, + /payment/, + /session/, + /auth/, + /verification/ +]; + +// Network first patterns (always try network first) +const NETWORK_FIRST_PATTERNS = [ + /\.js$/, + /\.jsx$/, + /\/src\//, + /api/, + /lightning/ +]; + +// Cache first patterns (static assets) +const CACHE_FIRST_PATTERNS = [ + /\.css$/, + /\.png$/, + /\.jpg$/, + /\.svg$/, + /\.ico$/, + /fonts/, + /logo/ +]; + +// Install event - cache static assets with better error handling +self.addEventListener('install', (event) => { + console.log('๐Ÿ”ง Service Worker installing...'); + + event.waitUntil( + caches.open(STATIC_CACHE) + .then(async (cache) => { + console.log('๐Ÿ“ฆ Caching static assets...'); + + // Cache assets one by one to handle failures gracefully + const cachePromises = STATIC_ASSETS.map(async (url) => { + try { + // Skip sensitive patterns + if (SENSITIVE_PATTERNS.some(pattern => pattern.test(url))) { + return; + } + + await cache.add(url); + console.log(`โœ… Cached: ${url}`); + } catch (error) { + console.warn(`โš ๏ธ Failed to cache ${url}:`, error.message); + // Continue with other assets even if one fails + } + }); + + await Promise.allSettled(cachePromises); + console.log('โœ… Static assets caching completed'); + + // Force activation of new service worker + return self.skipWaiting(); + }) + .catch((error) => { + console.error('โŒ Failed to open cache:', error); + // Still skip waiting to activate the service worker + return self.skipWaiting(); + }) + ); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => { + console.log('๐Ÿš€ Service Worker activating...'); + + event.waitUntil( + caches.keys() + .then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== STATIC_CACHE && + cacheName !== DYNAMIC_CACHE && + cacheName !== CACHE_NAME) { + console.log('๐Ÿ—‘๏ธ Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + .then(() => { + console.log('โœ… Service Worker activated'); + // Claim all clients immediately + return self.clients.claim(); + }) + ); +}); + +// Fetch event - handle requests with security-aware caching +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url); + + // Skip non-GET requests + if (event.request.method !== 'GET') { + return; + } + + // Skip sensitive endpoints + if (SENSITIVE_PATTERNS.some(pattern => pattern.test(url.pathname))) { + console.log('๐Ÿ”’ Skipping cache for sensitive endpoint:', url.pathname); + return; + } + + // Skip chrome-extension and non-http requests + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + return; + } + + event.respondWith(handleRequest(event.request)); +}); + +// Smart request handling with security considerations +async function handleRequest(request) { + const url = new URL(request.url); + + try { + // Strategy 1: Cache First (for static assets) + if (CACHE_FIRST_PATTERNS.some(pattern => pattern.test(url.pathname))) { + return await cacheFirst(request); + } + + // Strategy 2: Network First (for dynamic content and security-critical files) + if (NETWORK_FIRST_PATTERNS.some(pattern => pattern.test(url.pathname))) { + return await networkFirst(request); + } + + // Strategy 3: Stale While Revalidate (for main pages) + return await staleWhileRevalidate(request); + + } catch (error) { + console.error('โŒ Request handling failed:', error); + return await handleOffline(request); + } +} + +// Cache First strategy with Response cloning fix +async function cacheFirst(request) { + const cachedResponse = await caches.match(request); + if (cachedResponse) { + return cachedResponse; + } + + try { + const networkResponse = await fetch(request); + if (networkResponse && networkResponse.ok) { + // Clone the response before using it + const responseToCache = networkResponse.clone(); + const cache = await caches.open(STATIC_CACHE); + cache.put(request, responseToCache); + } + return networkResponse; + } catch (error) { + console.warn('โš ๏ธ Cache-first strategy failed:', error.message); + return await handleOffline(request); + } +} + +// Network First strategy with Response cloning fix +async function networkFirst(request) { + try { + const networkResponse = await fetch(request); + if (networkResponse && networkResponse.ok) { + // Only cache non-sensitive successful responses + if (!SENSITIVE_PATTERNS.some(pattern => pattern.test(request.url))) { + // Clone the response before caching + const responseToCache = networkResponse.clone(); + const cache = await caches.open(DYNAMIC_CACHE); + cache.put(request, responseToCache); + } + } + return networkResponse; + } catch (error) { + console.warn('โš ๏ธ Network-first strategy failed:', error.message); + const cachedResponse = await caches.match(request); + if (cachedResponse) { + return cachedResponse; + } + return await handleOffline(request); + } +} + +// Stale While Revalidate strategy with Response cloning fix +async function staleWhileRevalidate(request) { + const cachedResponse = await caches.match(request); + + const networkResponsePromise = fetch(request) + .then((networkResponse) => { + if (networkResponse && networkResponse.ok && + !SENSITIVE_PATTERNS.some(pattern => pattern.test(request.url))) { + // Clone the response before caching + const responseToCache = networkResponse.clone(); + caches.open(DYNAMIC_CACHE) + .then(cache => cache.put(request, responseToCache)) + .catch(error => console.warn('โš ๏ธ Cache update failed:', error.message)); + } + return networkResponse; + }) + .catch(error => { + console.warn('โš ๏ธ Network request failed:', error.message); + return null; + }); + + return cachedResponse || networkResponsePromise || handleOffline(request); +} + +// Offline fallback +async function handleOffline(request) { + const url = new URL(request.url); + + // For navigation requests, return cached index.html + if (request.destination === 'document') { + const cachedIndex = await caches.match('/'); + if (cachedIndex) { + return cachedIndex; + } + } + + // For images, return a placeholder or cached version + if (request.destination === 'image') { + return new Response( + 'Offline', + { headers: { 'Content-Type': 'image/svg+xml' } } + ); + } + + // Return a generic offline response + return new Response( + JSON.stringify({ error: 'Offline', message: 'Network unavailable' }), + { + status: 503, + statusText: 'Service Unavailable', + headers: { 'Content-Type': 'application/json' } + } + ); +} + +// Background sync for failed requests +self.addEventListener('sync', (event) => { + console.log('๐Ÿ”„ Background sync triggered:', event.tag); + + if (event.tag === 'retry-failed-requests') { + event.waitUntil(retryFailedRequests()); + } +}); + +// Retry failed requests when back online +async function retryFailedRequests() { + console.log('๐Ÿ”„ Retrying failed requests...'); +} + +// Push notification handler +self.addEventListener('push', (event) => { + console.log('๐Ÿ“จ Push notification received'); + +}); + +// Notification click handler +self.addEventListener('notificationclick', (event) => { + console.log('๐Ÿ”” Notification clicked'); + event.notification.close(); + + event.waitUntil( + clients.openWindow('/') + ); +}); + +// Message handler for communication with main thread +self.addEventListener('message', (event) => { + console.log('๐Ÿ’ฌ Message from main thread:', event.data); + + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } + + if (event.data && event.data.type === 'CACHE_CLEAR') { + event.waitUntil(clearCaches()); + } + + if (event.data && event.data.type === 'CACHE_STATUS') { + event.waitUntil(getCacheStatus().then(status => { + event.ports[0].postMessage(status); + })); + } +}); + +// Clear all caches +async function clearCaches() { + const cacheNames = await caches.keys(); + await Promise.all( + cacheNames.map(cacheName => caches.delete(cacheName)) + ); + console.log('๐Ÿ—‘๏ธ All caches cleared'); +} + +// Get cache status +async function getCacheStatus() { + const cacheNames = await caches.keys(); + const status = {}; + + for (const cacheName of cacheNames) { + const cache = await caches.open(cacheName); + const keys = await cache.keys(); + status[cacheName] = keys.length; + } + + return status; +} + +// Error handler +self.addEventListener('error', (event) => { + console.error('โŒ Service Worker error:', event.error); +}); + +// Unhandled rejection handler +self.addEventListener('unhandledrejection', (event) => { + console.error('โŒ Service Worker unhandled rejection:', event.reason); +}); + +console.log('๐Ÿ”ง SecureBit.chat Service Worker loaded - Enhanced Security Edition v4.01.212'); \ No newline at end of file