├── .gitignore ├── Notification.gif ├── README.md ├── Service-Worker-LifeCycle.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-splash-1125.png ├── apple-splash-1242.png ├── apple-splash-1536.png ├── apple-splash-1668.png ├── apple-splash-2048.png ├── apple-splash-640.png ├── apple-splash-750.png ├── apple-touch-icon.png ├── cache-fallback.png ├── css ├── main.css └── normalize.min.css ├── demo-service-workers.gif ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── img ├── aths.png ├── cassidy.jpg ├── cramer.jpg ├── duffy.jpg ├── gabor.jpg └── share.png ├── index.html ├── js └── main.js ├── manifest.json ├── mstile-150x150.png ├── package-lock.json ├── package.json ├── push-messagin-flow.png ├── safari-pinned-tab.svg ├── service-worker-installed.png └── sw.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.log 3 | npm-debug.log 4 | yarn-error.log 5 | .DS_Store 6 | build/ 7 | node_modules/ 8 | dist/ 9 | .cache -------------------------------------------------------------------------------- /Notification.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/Notification.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PWA Concepts 2 | > * A demo for PWA Concepts. 3 | 4 | ## Serve Pages Offline using Service Workers Demo :video_camera: 5 | 6 | ![](demo-service-workers.gif) 7 | 8 | ## [Sending Notifications in a PWA Demo](https://github.com/imranhsayed/pwa-concepts/tree/send-notification-user-nav) :video_camera: 9 | ![](Notification.gif) 10 | 11 | ## Getting Started :rocket: 12 | 13 | These instructions will get you a copy of the project up and running on your local machine for development purposes. 14 | 15 | ### Prerequisites :page_facing_up: 16 | 17 | Basic knowledge of HTML CSS and JavaScript. 18 | 19 | ## Service Workers :construction_worker: 20 | 21 | ### What are Service Workers? 22 | A service worker is an event-driven javascript file, that is run in your browser in the background, separate from your webpage. 23 | 24 | * act as a caching agent 25 | * handle network requests, 26 | * store content for offline usage, using caching and 27 | * handle push messaging even when your browser is closed. 28 | 29 | ### How Service Workers work? 30 | * It provides a persistence medium for you to keep network requests like other pages, images, scripts, CSS files, etc. in a controllable cache. 31 | * When a network request is made it passed through the service worker where you can decide if you will return the cached response or make the network round trip 32 | * The Cache API provides the methods you can programmatically use to manage how network responses are cached. 33 | * Responses are stored in the cache as a key value pair, where the key is the Request and the Response 34 | * Using a fetch event handler you can intercept all the requests, interrogate the Request object and execute your caching strategy. 35 | 36 | * So in below example, when any network request is made, we intercept that request, 37 | we use using `cache.match()` to check if we get response from cache, if not make a fetch request to network ( `fetch(event.request)` ), 38 | put the new response in cache ( `cache.put()` ) and return the response. Check the demo picture. 39 | 40 | ```ruby 41 | self.addEventListener("fetch", function(event) { 42 | event.respondWith( 43 | caches.open( cacheName ) 44 | .then( cache => { 45 | 46 | return cache.match( event.request ) 47 | .then(response => { 48 | 49 | // Check if you get response from cache, using cache.match(), if not make a fetch request to network , put the new response in cache and return the response 50 | return response || fetch(event.request) 51 | .then(function(response) { 52 | cache.put(event.request, response.clone()); 53 | return response; 54 | }); 55 | }) 56 | } ) 57 | ); 58 | }); 59 | ``` 60 | 61 | ![](cache-fallback.png) 62 | 63 | ## Steps to Create a Progressive Web App: 64 | 65 | 1. We register a Service Worker ( in main.js ) 66 | 2. Create a Service Worker file called `sw.js` and perform the below operations inside that. 67 | 3. Define `cacheName` and paths for the files to be cached inside `sw.js` 68 | 4. We listen to `install` event and `cache all files` we defined above, when the service worker is installed. 69 | 5. We listen to `activate` event and `delete the old version` of the cache, if there is a new version of cache available 70 | 6. We listen to the `fetch` event, the request is made on PWA, we `fetch the content from the cache` if its available otherwise we make a network request. 71 | 7. Add the icons for PWA 72 | 8. Add a `manifest.json` file and add the required fields and values 73 | 9. Add relevant `meta tags`, link your manifest.json file and include your main.js file, in `index.html` 74 | 10. Once done perform a `lighthouse audit` your site for PWA , under performance tab in chrome developer tool. 75 | 76 | 77 | #### Registration 78 | ![](service-workers.png) 79 | 80 | ### Installation 81 | ![](service-worker-installed.png) 82 | 83 | ### Cache Storage 84 | ![](cache-storage.png) 85 | 86 | ## Service worker lifecycle 87 | A service worker goes through three steps in its lifecycle: 88 | 89 | 1. Registration 90 | 2. Installation 91 | 3. Activation 92 | 93 | ![](Service-Worker-LifeCycle.png) 94 | 95 | ### 1. Registration of Service Worker 96 | To install a service worker, you need to register it in your main JavaScript code. Registration tells the browser where your service worker is located, and to start installing it in the background. Let's look at an example: 97 | 98 | ```ruby 99 | if( 'serviceWorker' in navigator ) { 100 | navigator.serviceWorker 101 | .register( './service-worker-cached.js' ) 102 | .then( registeration => console.log('Registration successful, scope is:', registeration.scope) ) 103 | .catch( err => console.log('Service worker registration failed, error:', err) ); 104 | } 105 | ``` 106 | 107 | ### 2. Installation of Service Worker 108 | A service worker installation triggers an install event in the installing service worker. 109 | 110 | ```ruby 111 | // Listen for install event, set callback 112 | self.addEventListener('install', ( event ) => { 113 | // Perform some task 114 | }); 115 | ``` 116 | 117 | ### 2. Activation of Service Worker 118 | 119 | ```ruby 120 | self.addEventListener( 'activate', ( event ) => { 121 | console.log( 'Service worker Activated' ) 122 | } ); 123 | ``` 124 | 125 | ## What is a [manifest file](https://developers.google.com/web/fundamentals/web-app-manifest/) for Web App? 126 | 127 | * The web app manifest is a simple JSON file that tells the browser about your web application and how it should behave when 'installed' on the user's mobile device or desktop. 128 | * Having a manifest is required by Chrome to show the Add to Home Screen prompt. 129 | * A typical manifest file includes information about the app name, icons it should use, the start_url it should start at when launched, and more. 130 | 131 | ## How does push notification work? 132 | 133 | > You need to ensure that you change the cache version name in sw.js file , every time you want to send a notification otherwise your pwa will pick up the data from cache and won’t have the new changes 134 | Notifications are shown from the service worker not the web app. The service worker goes on its own thread, independent of the app. So this is what allows it to display notifications, even when the app is not action. 135 | This means when we send notification, the serviceWorker does not have to be active, but just registered. So as soon as the serviceWorker gets registered we can send the notification. 136 | 137 | ```ruby 138 | if ( 'serviceWorker' in navigator ) { 139 | 140 | navigator.serviceWorker.register( '/sw.js' ) 141 | .then( ( res ) => { 142 | console.warn( `Sevice Worker Registered ${res.scope}` ); 143 | 144 | // Check if notifications are supported 145 | if ( 'Notification' in window ) { 146 | console.warn( 'Notifications are supported' ); 147 | 148 | // Request permission from user to send notifications. 149 | Notification.requestPermission().then( ( status ) => { 150 | 151 | if ( 'granted' === status ) { 152 | // When service worker is ready to show a notification, show the notification in the promise method 153 | navigator.serviceWorker.ready.then( ( registration ) => registration.showNotification( 'New Notification' ) ); 154 | } 155 | } ) 156 | } 157 | } ) 158 | .catch( err => console.warn( 'SW registration failed' + err ) ) 159 | } 160 | ``` 161 | 162 | ## Push Notification Data Flow 163 | 164 | * Client access PWA > PWA persmission to send notification > Permission granted > Subsription object is created > Store the notification data in subscription 165 | * HTTP Post request > to Messaging Service ( like firebase ) > Messing server sends push message to client > App is awakened > Push message is routed to the correct Service Worker 166 | * User clicks on Notification > Code in the Notification Clicked event executes 167 | 168 | ![](push-messagin-flow.png) 169 | 170 | ## Installation :wrench: 171 | 172 | 1. Clone this repo by running `git clone git@github.com:imranhsayed/pwa-concepts.git` 173 | 2. `cd pwa-concepts` 174 | 3. Install `Live Server` plugin from VS Code 175 | 176 | ## Branches Information 177 | 178 | 1. [simple-progressive-web-app](https://github.com/imranhsayed/pwa-concepts/tree/simple-progressive-web-app) A Simple Progressive Web App 179 | 2. [pwa-with-custom-prompt](https://github.com/imranhsayed/pwa-concepts/tree/pwa-with-custom-prompt) A Simple Progressive Web App with custom Add To Home Screen Mobile Prompt. 180 | 3. [service-worker-app](https://github.com/imranhsayed/pwa-concepts/tree/service-worker-app) A simple Service worker app. 181 | 182 | 4. [pwa-app-http-server](https://github.com/imranhsayed/pwa-concepts/tree/pwa-app-http-server) A Progressive Web App5. [send-notification-user-nav](https://github.com/imranhsayed/pwa-concepts/tree/send-notification-user-nav) Example of sending Notification in a PWA app. And when the user clicks on the notification, we can navigate him the section of the page we want 183 | 5. [send-notification-user-nav](https://github.com/imranhsayed/pwa-concepts/tree/send-notification-user-nav) Example of sending Notification in a PWA app. And when the user clicks on the notification, we can navigate him the section of the page we want 184 | 185 | ##### Command 186 | * `npm run start` Starts your development server on [http://localhost:8081](http://localhost:8081) 187 | * `Cmd + Shift + P > Type Clear Console history` Shortcut to clear cache data. 188 | 189 | ## Useful Link :point_right: 190 | 191 | 1. [Genrate Favicon](https://realfavicongenerator.net) 192 | 193 | ## Training Links :mortar_board: 194 | 195 | 1. [Service Workers Documentation](https://developers.google.com/web/ilt/pwa/introduction-to-service-worker) 196 | 2. [Service Worker Strategy Cookbook](https://serviceworke.rs/) 197 | 198 | ## Contributing :busts_in_silhouette: 199 | 200 | Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us. 201 | 202 | ## Versioning :bookmark_tabs: 203 | 204 | I use [Git](https://github.com/) for versioning. 205 | 206 | ## Author :bust_in_silhouette: 207 | 208 | * **[Imran Sayed](https://codeytek.com)** 209 | 210 | ## License :page_with_curl: 211 | 212 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 213 | -------------------------------------------------------------------------------- /Service-Worker-LifeCycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/Service-Worker-LifeCycle.png -------------------------------------------------------------------------------- /android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/android-chrome-192x192.png -------------------------------------------------------------------------------- /android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/android-chrome-512x512.png -------------------------------------------------------------------------------- /apple-splash-1125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-1125.png -------------------------------------------------------------------------------- /apple-splash-1242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-1242.png -------------------------------------------------------------------------------- /apple-splash-1536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-1536.png -------------------------------------------------------------------------------- /apple-splash-1668.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-1668.png -------------------------------------------------------------------------------- /apple-splash-2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-2048.png -------------------------------------------------------------------------------- /apple-splash-640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-640.png -------------------------------------------------------------------------------- /apple-splash-750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-splash-750.png -------------------------------------------------------------------------------- /apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/apple-touch-icon.png -------------------------------------------------------------------------------- /cache-fallback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/cache-fallback.png -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /*! HTML5 Boilerplate v5.0 | MIT License | http://h5bp.com/ */ 2 | 3 | html { 4 | color: #222; 5 | font-size: 1em; 6 | line-height: 1.4; 7 | } 8 | 9 | ::-moz-selection { 10 | background: #b3d4fc; 11 | text-shadow: none; 12 | } 13 | 14 | ::selection { 15 | background: #b3d4fc; 16 | text-shadow: none; 17 | } 18 | 19 | hr { 20 | display: block; 21 | height: 1px; 22 | border: 0; 23 | border-top: 1px solid #ccc; 24 | margin: 1em 0; 25 | padding: 0; 26 | } 27 | 28 | audio, 29 | canvas, 30 | iframe, 31 | img, 32 | svg, 33 | video { 34 | vertical-align: middle; 35 | } 36 | 37 | fieldset { 38 | border: 0; 39 | margin: 0; 40 | padding: 0; 41 | } 42 | 43 | textarea { 44 | resize: vertical; 45 | } 46 | 47 | .browserupgrade { 48 | margin: 0.2em 0; 49 | background: #ccc; 50 | color: #000; 51 | padding: 0.2em 0; 52 | } 53 | 54 | 55 | /* ===== Initializr Styles ================================================== 56 | Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template 57 | ========================================================================== */ 58 | 59 | body { 60 | font: 16px/26px Helvetica, Helvetica Neue, Arial; 61 | } 62 | 63 | .wrapper { 64 | width: 90%; 65 | margin: 0 5%; 66 | } 67 | 68 | /* =================== 69 | ALL: Blue Theme 70 | =================== */ 71 | 72 | .header-container { 73 | border-bottom: 20px solid #264de4; 74 | position: fixed; 75 | width: 100%; 76 | top: 0px; 77 | } 78 | 79 | .footer-container, 80 | .main aside { 81 | border-top: 20px solid #264de4; 82 | } 83 | 84 | .header-container, 85 | .footer-container, 86 | .main aside { 87 | background: #2965f1; 88 | } 89 | 90 | .title { 91 | color: white; 92 | } 93 | 94 | /* ============== 95 | MOBILE: Menu 96 | ============== */ 97 | 98 | nav ul { 99 | margin: 0; 100 | padding: 0; 101 | list-style-type: none; 102 | } 103 | 104 | nav a { 105 | display: block; 106 | margin-bottom: 10px; 107 | padding: 15px 0; 108 | 109 | text-align: center; 110 | text-decoration: none; 111 | font-weight: bold; 112 | 113 | color: white; 114 | background: #264de4; 115 | } 116 | 117 | nav{ 118 | display: none; 119 | } 120 | 121 | nav a:hover, 122 | nav a:visited { 123 | color: white; 124 | } 125 | 126 | nav a:hover { 127 | text-decoration: underline; 128 | } 129 | 130 | /* ============== 131 | MOBILE: Main 132 | ============== */ 133 | 134 | .main { 135 | padding: 30px 0; 136 | } 137 | 138 | .main article h1 { 139 | font-size: 2em; 140 | } 141 | 142 | .main aside { 143 | color: white; 144 | padding: 0px 5% 10px; 145 | } 146 | 147 | .footer-container footer { 148 | color: white; 149 | padding: 20px 0; 150 | } 151 | 152 | /* =============== 153 | ALL: IE Fixes 154 | =============== */ 155 | 156 | .ie7 .title { 157 | padding-top: 20px; 158 | } 159 | 160 | /* ========================================================================== 161 | Author's custom styles 162 | ========================================================================== */ 163 | 164 | .subtitle{ 165 | font-size: 1.1em; 166 | font-style: italic; 167 | font-weight: bolder; 168 | } 169 | 170 | .responsiveImg { 171 | margin: auto; 172 | max-width: 128px; 173 | border-radius: 50%; 174 | padding: 0px 20px 10px 10px; 175 | } 176 | 177 | .main header, .main section, .main footer{ 178 | padding: 10px; 179 | margin-bottom: 20px; 180 | } 181 | 182 | .main header h1, .main section h2, .facultyContainer h4{ 183 | background: #2965f1; 184 | color: white; 185 | padding: 10px; 186 | border-top: 20px solid #264de4; 187 | } 188 | 189 | .facultyContainer{ 190 | padding: 5px; 191 | 192 | } 193 | .facultyImage{ 194 | float: left; 195 | width: 30%; 196 | height: 30%; 197 | min-width: 180px; 198 | margin: auto; 199 | text-align: center; 200 | } 201 | 202 | aside a{ 203 | color: white; 204 | text-decoration: none; 205 | } 206 | 207 | .footerLinks{ 208 | float: right; 209 | text-align: right; 210 | } 211 | 212 | .footerLinks a{ 213 | color: white; 214 | text-decoration: none; 215 | } 216 | 217 | #trigger{ 218 | float: right; 219 | line-height: 10px; 220 | padding: 5px; 221 | height: 32px; 222 | width: 32px; 223 | position: fixed; 224 | margin: 5px 10px 0px 15px; 225 | cursor: pointer; 226 | cursor: hand; 227 | background-color: #264de4; 228 | border-radius: 21px; 229 | vertical-align: text-top; 230 | right: 10px; 231 | top: 10px; 232 | } 233 | .triggerLine{ 234 | display: block; 235 | width: calc(100% - 9px); 236 | text-align: center; 237 | height: 2px; 238 | background-color: white; 239 | margin: 6px auto; 240 | border-radius: 2px; 241 | } 242 | .main-container{ 243 | margin-top: 40px; 244 | } 245 | 246 | #addToHomeScreen { 247 | margin-top: 20px; 248 | padding: 2px 6px 14px 14px; 249 | display: none; 250 | } 251 | 252 | #addToHomeScreen img { 253 | max-width: 57px; 254 | margin: 10px; 255 | float: left; 256 | } 257 | 258 | #addToHomeScreen button { 259 | background-color: #264de4; 260 | color: #fff; 261 | border: 1px solid #2965f1; 262 | margin: 5px 10px; 263 | } 264 | 265 | 266 | /* ========================================================================== 267 | Media Queries 268 | ========================================================================== */ 269 | 270 | @media only screen and (min-width: 480px) { 271 | 272 | /* ==================== 273 | INTERMEDIATE: Menu 274 | ==================== */ 275 | 276 | nav a { 277 | float: left; 278 | width: 27%; 279 | margin: 0 1.7%; 280 | padding: 25px 2%; 281 | margin-bottom: 0; 282 | } 283 | 284 | nav li:first-child a { 285 | margin-left: 0; 286 | } 287 | 288 | nav li:last-child a { 289 | margin-right: 0; 290 | } 291 | #trigger{ 292 | display: block; 293 | } 294 | 295 | 296 | /* ======================== 297 | INTERMEDIATE: IE Fixes 298 | ======================== */ 299 | 300 | nav ul li { 301 | display: inline; 302 | } 303 | 304 | .oldie nav a { 305 | margin: 0 0.7%; 306 | } 307 | } 308 | 309 | @media only screen and (min-width: 768px) { 310 | 311 | /* ==================== 312 | WIDE: CSS3 Effects 313 | ==================== */ 314 | 315 | .header-container, 316 | .main aside, 317 | .main header, 318 | .main section, 319 | .main footer { 320 | -webkit-box-shadow: 0 5px 10px #aaa; 321 | -moz-box-shadow: 0 5px 10px #aaa; 322 | box-shadow: 0 5px 10px #aaa; 323 | } 324 | 325 | /* ============ 326 | WIDE: Menu 327 | ============ */ 328 | 329 | .title { 330 | float: left; 331 | } 332 | 333 | nav { 334 | float: right; 335 | width: 38%; 336 | display: block !important; 337 | } 338 | #trigger{ 339 | display: none; 340 | } 341 | 342 | /* ============ 343 | WIDE: Main 344 | ============ */ 345 | 346 | .main article { 347 | float: left; 348 | width: 57%; 349 | } 350 | 351 | .main aside { 352 | float: right; 353 | width: 28%; 354 | } 355 | 356 | .main-container{ 357 | margin-top: 100px; 358 | } 359 | } 360 | 361 | @media only screen and (min-width: 1140px) { 362 | 363 | /* =============== 364 | Maximal Width 365 | =============== */ 366 | 367 | .wrapper { 368 | width: 1026px; /* 1140px - 10% for margins */ 369 | margin: 0 auto; 370 | } 371 | } 372 | 373 | /* ========================================================================== 374 | Helper classes 375 | ========================================================================== */ 376 | 377 | .hidden { 378 | display: none !important; 379 | visibility: hidden; 380 | } 381 | 382 | .visuallyhidden { 383 | border: 0; 384 | clip: rect(0 0 0 0); 385 | height: 1px; 386 | margin: -1px; 387 | overflow: hidden; 388 | padding: 0; 389 | position: absolute; 390 | width: 1px; 391 | } 392 | 393 | .visuallyhidden.focusable:active, 394 | .visuallyhidden.focusable:focus { 395 | clip: auto; 396 | height: auto; 397 | margin: 0; 398 | overflow: visible; 399 | position: static; 400 | width: auto; 401 | } 402 | 403 | .invisible { 404 | visibility: hidden; 405 | } 406 | 407 | .clearfix:before, 408 | .clearfix:after { 409 | content: " "; 410 | display: table; 411 | } 412 | 413 | .clearfix:after { 414 | clear: both; 415 | } 416 | 417 | .clearfix { 418 | *zoom: 1; 419 | } 420 | 421 | /* ========================================================================== 422 | Print styles 423 | ========================================================================== */ 424 | 425 | @media print { 426 | *, 427 | *:before, 428 | *:after { 429 | background: transparent !important; 430 | color: #000 !important; 431 | box-shadow: none !important; 432 | text-shadow: none !important; 433 | } 434 | 435 | a, 436 | a:visited { 437 | text-decoration: underline; 438 | } 439 | 440 | a[href]:after { 441 | content: " (" attr(href) ")"; 442 | } 443 | 444 | abbr[title]:after { 445 | content: " (" attr(title) ")"; 446 | } 447 | 448 | a[href^="#"]:after, 449 | a[href^="javascript:"]:after { 450 | content: ""; 451 | } 452 | 453 | pre, 454 | blockquote { 455 | border: 1px solid #999; 456 | page-break-inside: avoid; 457 | } 458 | 459 | thead { 460 | display: table-header-group; 461 | } 462 | 463 | tr, 464 | img { 465 | page-break-inside: avoid; 466 | } 467 | 468 | img { 469 | max-width: 100% !important; 470 | } 471 | 472 | p, 473 | h2, 474 | h3 { 475 | orphans: 3; 476 | widows: 3; 477 | } 478 | 479 | h2, 480 | h3 { 481 | page-break-after: avoid; 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /css/normalize.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} -------------------------------------------------------------------------------- /demo-service-workers.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/demo-service-workers.gif -------------------------------------------------------------------------------- /favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/favicon-16x16.png -------------------------------------------------------------------------------- /favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/favicon-32x32.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/favicon.ico -------------------------------------------------------------------------------- /img/aths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/img/aths.png -------------------------------------------------------------------------------- /img/cassidy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/img/cassidy.jpg -------------------------------------------------------------------------------- /img/cramer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/img/cramer.jpg -------------------------------------------------------------------------------- /img/duffy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/img/duffy.jpg -------------------------------------------------------------------------------- /img/gabor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/img/gabor.jpg -------------------------------------------------------------------------------- /img/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/img/share.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | NCC Computer Science 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 |

Computer Science

39 | 46 |
47 |
48 | 49 |
50 |
51 | 52 | 53 |
54 |

Install App

55 | NCC CS 56 | Add our app to home screen?
57 | No Thanks 58 | 59 |
60 | 61 |
62 |
63 |

Welcome!

64 |

65 | You've come to the right place! 66 |

67 |

68 | It’s an exciting – and lucrative – time to be a 69 | Computer Science major. At NCC, you’ll be grounded 70 | in Computer Science fundamentals and exposed to 71 | today’s employable technologies. Want to build 72 | Mobile Apps? Want to create state of the art web 73 | pages? Want to learn advanced topics? No worries 74 | – we’ve got you covered. 75 |

76 |

77 | Whether you are a transfer student looking to start 78 | an advanced degree or a programmer learning a new 79 | language or just looking to add skills to your tool 80 | set, NCC’s Computer Science Department has Courses 81 | and Programs to meet your needs. 82 |

83 | 84 |
85 |
86 |

Faculty

87 |

88 | NCC's Computer Science faculty possess an abundance 89 | of real world experience coupled with twenty-first century 90 | teaching abilities. The result is classes that ground 91 | students in theory while teaching them how to implement 92 | the technologies needed to thrive in today's economy. 93 |

94 |
95 |

96 | Professor Tom Duffy, Department Chair 97 |

98 |
99 |

100 | Tom Duffy 101 |

102 |

103 | (203) 857-6892 104 |

105 |

106 | tduffy@norwalk.edu 107 |

108 |
109 |

110 | 111 | Professor Tom Duffy is the Chair of the Computer 112 | Science Department and the Program Coordinator 113 | for the Computer Science degree as well as the Web 114 | Developer, Relational Database, and Smartphone App 115 | Development certificates. He teaches courses in Web 116 | Development, XML, Java, and Mobile Device Programming. 117 |

118 |

119 | Tom holds a Bachelor of Science degree in Mathematics 120 | and Master of Arts degree in Mathematics/Computer Science 121 | from Western Connecticut State University. He is the 122 | owner of Bright Moments Software – a software company 123 | specializing in Web Technologies. 124 |

125 |

126 | Tom has recently published Programming With Mobile Applications, his second book. The book is available from Cengage Learning. 127 |

128 |

129 | Back To Top 130 |

131 | 132 |
133 |
134 |

135 | Professor Patrick Cassidy 136 |

137 |
138 |

139 | Patrick Cassidy 140 |

141 |

142 | (203) 857-7336 143 |

144 |

145 | pcassidy@norwalk.edu 146 |

147 |
148 |

149 | Professor Cassidy is the Coordinator for the Computer 150 | Security degree and Networking Certificate programs. 151 | He is also the Main Contact for NCC’s Cisco Academy. 152 |

153 |

154 | Before coming to NCC, Prof. Cassidy was a Project 155 | Associate for the University of Michigan working out 156 | of the General Motors Plant in Tarrytown, NY. He has 157 | also taught at Westchester Community College in both 158 | the Mathematics and Computer Science departments. 159 |

160 |

161 | He holds a M.S. in Computer Science from Polytechnic 162 | University, a B.S. in Aeronautical Science from 163 | Embry Riddle Aeronautical University, and an A.S. 164 | in Mathematics and Science from Westchester Community 165 | College. Prof. Cassidy is a Cisco Certified Network 166 | Associate (CCNA) and Cisco Certified Academy Instructor 167 | (CCAI). He also holds multiple ratings from the 168 | FAA as well as being a Certified Flight 169 | Instructor – Instrument (CFII). 170 |

171 |

172 | Back To Top 173 |

174 |
175 |
176 |

177 | Professor Kerry Cramer 178 |

179 |
180 |

181 | Kerry Cramer 182 |

183 |

184 | (203) 857-3332 185 |

186 |

187 | kcramer@norwalk.edu 188 |

189 |
190 |

191 | Professor Kerry V. Cramer is an information technology 192 | professional with 30 years experience in computer 193 | programming, information technologies, and IT project 194 | management. Mr. Cramer has been an adjunct professor at 195 | Manhattanville College, and University of New Haven 196 | teaching several courses in the Computer Science 197 | curriculum as well as substitute teaching K-12 at 198 | schools in the Danbury, CT area. 199 |

200 |

Professor Cramer’s strengths 201 | include strong project management disciplines, 202 | technical, supervisory and team management skills in 203 | Internet, Lotus Notes, and legacy application development 204 | and maintenance environments as well as extensive college 205 | and professional recruiting experience. 206 |

207 |

208 | Back To Top 209 |

210 |
211 |
212 |

213 | Professor Charles Gabor 214 |

215 |
216 |

217 | Charles Gabor 218 |

219 |

220 | (203) 857-7315 221 |

222 |

223 | cgabor@norwalk.edu 224 |

225 |
226 |

227 | Professor Gabor teaches Database Development and Java courses. 228 | Before joining the NCC faculty he was a Lieutenant/Senior 229 | Military Instructor at the United States Naval Academy. 230 | Prior to that he was a Software Engineer at Pitney Bowes Inc. 231 |

232 |

233 | Professor Gabor holds a graduate certificate in Computer 234 | Science from Purdue University, a M.S. degree from 235 | the University of New Haven and a B.S degree in 236 | Applied Science from Charter Oak State College. 237 | He is a member of the Honor Society in Computer Science, 238 | Upsilon Pi Epsilon and a retired Commander in the U.S. Navy. 239 |

240 |

241 | Back To Top 242 |

243 |
244 | 245 |
246 |
247 |

Programs

248 |

249 | Our programs serve both traditional first-time students 250 | as well as professionals currently working in the field. 251 | The curriculum is flexible enough to meet the needs of 252 | students who wish to transfer to a baccalaureate 253 | institution and students preparing for immediate 254 | entry into the workplace. 255 |

256 |

257 | Degree Programs 258 |

259 |

260 | AS Computer Science 261 |

262 |

263 | AS Computer Security 264 |

265 |

266 | Certificate Programs 267 |

268 |

269 | Relational Database Development 270 |

271 |

272 | Smartphone App Development 273 |

274 |

275 | Web Developer 276 |

277 |

278 | Back To Top 279 |

280 |
281 |
282 |

Courses

283 |

284 | Computer Science courses at NCC not only prepare students to 285 | transfer into a baccalaureate institution. They also serve 286 | those students who wish to enter the workforce directly. 287 | All our courses expose students to the course's underlying CS 288 | theory as well as teach students how to implement those 289 | theories. The result is students who are prepared for 290 | whatever they choose to do next. 291 |

292 |

293 | Computer Science (CSC) 294 |

295 |

296 | Computer Technology (CST) 297 |

298 |

299 | Computer Applications (CSA) 300 |

301 |

302 | Back To Top 303 |

304 |
305 |
306 | 307 | 321 | 322 |
323 |
324 | 325 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | window.onhashchange = function(){ 2 | //Header is fixed, need to slide down some to see sectionHead 3 | setTimeout('scrollBy(0,-110)',10); 4 | }; 5 | var hidden = true; 6 | function toggleNav(){ 7 | if(hidden){ 8 | document.getElementsByTagName('nav')[0].style.display = 'block'; 9 | }else{ 10 | document.getElementsByTagName('nav')[0].style.display = 'none'; 11 | } 12 | hidden = !hidden; 13 | } 14 | 15 | const handlePushNotification = () => { 16 | // Check if notifications are supported 17 | if ( 'Notification' in window ) { 18 | console.warn( 'Notifications are supported' ); 19 | 20 | /** 21 | * Request permission from user to send notifications. 22 | * 23 | */ 24 | Notification.requestPermission().then( ( result ) => { 25 | console.warn( 'Notification Status', result ); 26 | if ( 'granted' === result ) { 27 | 28 | const options = { 29 | body: 'Check out the new GOW4', 30 | icon: 'android-chrome-192x192.png', 31 | data: { 32 | timeStamp: Date.now(), 33 | loc: 'index.html#info' 34 | }, 35 | actions: [ 36 | { action: 'go', title: 'Go Now' } 37 | ] 38 | }; 39 | 40 | sendNotification( 'New Notification', options ); 41 | } 42 | } ) 43 | } 44 | }; 45 | 46 | const sendNotification = ( title, options ) => { 47 | // When service worker is ready to show a notification, show the notification in the promise method 48 | navigator.serviceWorker.ready.then( ( registration ) => registration.showNotification( title, options ) ); 49 | 50 | }; 51 | 52 | // Check if the serviceWorker Object exists in the navigator object ( means if browser supports SW ) 53 | if ( 'serviceWorker' in navigator ) { 54 | 55 | /** 56 | * Register Service Worker 57 | * 'sw.js' is our service worker file 58 | */ 59 | navigator.serviceWorker.register( '/sw.js' ) 60 | .then( ( res ) => { 61 | console.warn( `Sevice Worker Registered ${res.scope}` ); 62 | // Handle Push Notification, only once the service worker is registered 63 | handlePushNotification(); 64 | } ) 65 | .catch( err => console.warn( 'SW registration failed' + err ) ) 66 | 67 | } else { 68 | console.warn( 'Service Workers not supported' ); 69 | } 70 | 71 | var installEvent; 72 | window.addEventListener( 'beforeinstallprompt', ( event ) => { 73 | 74 | console.warn( 'Before Install Prompt' ); 75 | 76 | // Store the beforeinstallprompt event pointer in installEvent for later use 77 | installEvent = event; 78 | 79 | // Prevent the Chrome 67 and older versions, to automatically show the default 'Add to Home Screen' prompt 80 | event.preventDefault(); 81 | 82 | // Show the custom 'Add to Home Screen' prompt that we created in index.html. 83 | document.getElementById( 'addToHomeScreen' ).style.display = 'block'; 84 | } ); 85 | 86 | const hidePrompt = () => { 87 | document.getElementById( 'addToHomeScreen' ).style.display = 'none'; 88 | }; 89 | 90 | const installApp = () => { 91 | // Once user installs the app, the prompt is no longer needed so we hide it 92 | hidePrompt(); 93 | 94 | // Use the beforeinstallprompt event pointer to call the prompt() and show our custom Add to Home Screen install prompt 95 | installEvent.prompt(); 96 | 97 | // Wait on the prompt promise to resolve. 98 | installEvent.userChoice.then( ( result ) => { 99 | ( 'accepted' === result.outcome ) ? console.warn( 'App installed' ) : console.warn( 'App not installed' ); 100 | 101 | 102 | }); 103 | }; 104 | 105 | window.addEventListener( 'appinstalled', ( event ) => { 106 | console.warn( 'appsinatlled event called' ); 107 | } ); 108 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NCC CS", 3 | "short_name": "NCC CS", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffc40d", 17 | "background_color": "#ffc40d", 18 | "display": "standalone", 19 | "start_url": "/index.html" 20 | } 21 | -------------------------------------------------------------------------------- /mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/mstile-150x150.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pwa-concepts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async": { 8 | "version": "1.5.2", 9 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 10 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 11 | "dev": true 12 | }, 13 | "colors": { 14 | "version": "1.0.3", 15 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 16 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", 17 | "dev": true 18 | }, 19 | "corser": { 20 | "version": "2.0.1", 21 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 22 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", 23 | "dev": true 24 | }, 25 | "debug": { 26 | "version": "3.2.6", 27 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 28 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 29 | "dev": true, 30 | "requires": { 31 | "ms": "^2.1.1" 32 | } 33 | }, 34 | "ecstatic": { 35 | "version": "3.3.2", 36 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 37 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 38 | "dev": true, 39 | "requires": { 40 | "he": "^1.1.1", 41 | "mime": "^1.6.0", 42 | "minimist": "^1.1.0", 43 | "url-join": "^2.0.5" 44 | } 45 | }, 46 | "eventemitter3": { 47 | "version": "3.1.2", 48 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", 49 | "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", 50 | "dev": true 51 | }, 52 | "follow-redirects": { 53 | "version": "1.7.0", 54 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", 55 | "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", 56 | "dev": true, 57 | "requires": { 58 | "debug": "^3.2.6" 59 | } 60 | }, 61 | "he": { 62 | "version": "1.2.0", 63 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 64 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 65 | "dev": true 66 | }, 67 | "http-proxy": { 68 | "version": "1.17.0", 69 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", 70 | "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", 71 | "dev": true, 72 | "requires": { 73 | "eventemitter3": "^3.0.0", 74 | "follow-redirects": "^1.0.0", 75 | "requires-port": "^1.0.0" 76 | } 77 | }, 78 | "http-server": { 79 | "version": "0.11.1", 80 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz", 81 | "integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==", 82 | "dev": true, 83 | "requires": { 84 | "colors": "1.0.3", 85 | "corser": "~2.0.0", 86 | "ecstatic": "^3.0.0", 87 | "http-proxy": "^1.8.1", 88 | "opener": "~1.4.0", 89 | "optimist": "0.6.x", 90 | "portfinder": "^1.0.13", 91 | "union": "~0.4.3" 92 | } 93 | }, 94 | "mime": { 95 | "version": "1.6.0", 96 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 97 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 98 | "dev": true 99 | }, 100 | "minimist": { 101 | "version": "1.2.0", 102 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 103 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 104 | "dev": true 105 | }, 106 | "mkdirp": { 107 | "version": "0.5.1", 108 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 109 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 110 | "dev": true, 111 | "requires": { 112 | "minimist": "0.0.8" 113 | }, 114 | "dependencies": { 115 | "minimist": { 116 | "version": "0.0.8", 117 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 118 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 119 | "dev": true 120 | } 121 | } 122 | }, 123 | "ms": { 124 | "version": "2.1.1", 125 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 126 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 127 | "dev": true 128 | }, 129 | "opener": { 130 | "version": "1.4.3", 131 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", 132 | "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", 133 | "dev": true 134 | }, 135 | "optimist": { 136 | "version": "0.6.1", 137 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 138 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 139 | "dev": true, 140 | "requires": { 141 | "minimist": "~0.0.1", 142 | "wordwrap": "~0.0.2" 143 | }, 144 | "dependencies": { 145 | "minimist": { 146 | "version": "0.0.10", 147 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 148 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", 149 | "dev": true 150 | } 151 | } 152 | }, 153 | "portfinder": { 154 | "version": "1.0.20", 155 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", 156 | "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", 157 | "dev": true, 158 | "requires": { 159 | "async": "^1.5.2", 160 | "debug": "^2.2.0", 161 | "mkdirp": "0.5.x" 162 | }, 163 | "dependencies": { 164 | "debug": { 165 | "version": "2.6.9", 166 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 167 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 168 | "dev": true, 169 | "requires": { 170 | "ms": "2.0.0" 171 | } 172 | }, 173 | "ms": { 174 | "version": "2.0.0", 175 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 176 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 177 | "dev": true 178 | } 179 | } 180 | }, 181 | "qs": { 182 | "version": "2.3.3", 183 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", 184 | "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", 185 | "dev": true 186 | }, 187 | "requires-port": { 188 | "version": "1.0.0", 189 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 190 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", 191 | "dev": true 192 | }, 193 | "union": { 194 | "version": "0.4.6", 195 | "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz", 196 | "integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=", 197 | "dev": true, 198 | "requires": { 199 | "qs": "~2.3.3" 200 | } 201 | }, 202 | "url-join": { 203 | "version": "2.0.5", 204 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 205 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", 206 | "dev": true 207 | }, 208 | "wordwrap": { 209 | "version": "0.0.3", 210 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 211 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 212 | "dev": true 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pwa-concepts", 3 | "version": "1.0.0", 4 | "description": "> * A demo for PWA Concepts.", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "http-server -c-1 -p 8081" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/imranhsayed/pwa-concepts.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/imranhsayed/pwa-concepts/issues" 18 | }, 19 | "homepage": "https://github.com/imranhsayed/pwa-concepts#readme", 20 | "devDependencies": { 21 | "http-server": "^0.11.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /push-messagin-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/push-messagin-flow.png -------------------------------------------------------------------------------- /safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 163 | 212 | 286 | 351 | 442 | 446 | 451 | 453 | 458 | 461 | 467 | 472 | 477 | 480 | 483 | 488 | 493 | 497 | 502 | 507 | 510 | 511 | 512 | -------------------------------------------------------------------------------- /service-worker-installed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imranhsayed/pwa-concepts/a90f703895b0f17891de3d4915164b9fa585fe54/service-worker-installed.png -------------------------------------------------------------------------------- /sw.js: -------------------------------------------------------------------------------- 1 | // Cache version. 2 | const cacheName = 'CSv4'; 3 | 4 | /** 5 | * Paths for the files to be cached. 6 | * 7 | * There will be only one download of file on each route. 8 | * Once its downloaded, the page when that route is requested will be served from the cache 9 | * 10 | * @type {string[]} 11 | */ 12 | const cachedFiles = [ 13 | '/', 14 | '/index.html', 15 | '/manifest.json', 16 | '/js/main.js', 17 | '/css/main.css', 18 | '/css/normalize.min.css', 19 | '/img/cassidy.jpg', 20 | '/img/cramer.jpg', 21 | '/img/duffy.jpg', 22 | '/img/gabor.jpg' 23 | ]; 24 | 25 | /** 26 | * We listen to the install event, When you register a SW , it gets registered and installed. 27 | * And when it gets installed, we cache the pages content. 28 | * 29 | * 'self' keyword refers to the current service worker 30 | * self.skipWaiting() allows it to skip waiting for the old service worker 31 | */ 32 | self.addEventListener( 'install', ( event ) => { 33 | 34 | console.warn( 'Service Worker Installed' ); 35 | 36 | /** 37 | * Add the files to the cache 38 | * 39 | * event has a method called waitUntil, which takes a promise. In this case caches.open() 40 | * The caches.open() takes the cache version as cacheName and returns the cache object in promise. 41 | * This cache object has a method called addAll() which adds the files that you pass as param to the cache memory. 42 | * 43 | * Then we call self.skipWaiting() So that it does not wait for the previous version of the cache and goes the next one. 44 | */ 45 | event.waitUntil( 46 | caches.open( cacheName ) 47 | .then( cache => { 48 | 49 | console.warn( 'Caching Files'); 50 | return cache.addAll( cachedFiles ); 51 | 52 | } ) 53 | .then( () => self.skipWaiting() ) 54 | .catch( err => console.warn( err ) ) 55 | ); 56 | }); 57 | 58 | 59 | /* 60 | * Check the global cache variable, If the new cache version is not the same as the old one delete the old cache 61 | * , when the SW is activated 62 | * 63 | */ 64 | self.addEventListener( 'activate', ( event ) => { 65 | 66 | console.warn( 'Service Worker Activated' ); 67 | 68 | /** 69 | * Global cache object has a keys method that contains the previous cached items 70 | */ 71 | event.waitUntil( 72 | caches.keys() 73 | .then( keyList => { 74 | 75 | console.warn( 'Check if there is a new cache version'); 76 | 77 | // The Promise.all() will fail if any promise method inside of it fails 78 | return Promise.all( keyList.map( key => { 79 | if ( key !== cacheName ) { 80 | 81 | console.warn( 'Deleting old Cached File with Key', key ); 82 | 83 | // Delete that cache with that key 84 | return caches.delete( key ); 85 | } 86 | } ) ) 87 | 88 | } ) 89 | ); 90 | 91 | // This helps, service Worker claims all of the clients in the scope of the SW. 92 | // So that any further events apply to all the pages 93 | return self.clients.claim(); 94 | }); 95 | 96 | /** 97 | * The fetch event is called when any request is made on PWA. 98 | * 99 | * Then we can respond with the cached files. 100 | */ 101 | self.addEventListener( 'fetch', ( event ) => { 102 | 103 | console.warn( `Fetch event occured on url: ${event.request.url}` ); 104 | 105 | /** 106 | * respondWith() takes a promise 107 | * Global cache object contains a match(), which will look for a corresponding page, based on the URL and the method and 108 | * return the response. 109 | */ 110 | event.respondWith( 111 | 112 | // This will look for the requested url ( event.request ) into cache first 113 | caches.match( event.request ) 114 | .then( response => { 115 | 116 | /** 117 | * If the requested url is present in the cache it will return response from cache, 118 | * else make a network request for that url using fetch(). 119 | * So response variable is the cached page here. 120 | * If the first operand before || evaluates to true, the second is not evaluated. 121 | */ 122 | return response || fetch( event.request ) 123 | } ) 124 | ); 125 | } ); 126 | 127 | 128 | // Close Notification. 129 | const closeNotification = ( msg, event ) => { 130 | console.warn( msg, event.notification ); 131 | event.notification.close(); 132 | }; 133 | 134 | // Listen to the notification close event. 135 | self.addEventListener( 'notificationclose', ( event ) => { 136 | console.warn( 'came' ); 137 | closeNotification( 'Notification Closed', event ); 138 | } ); 139 | 140 | // Listen to when click event on Notification bar 141 | self.addEventListener( 'notificationclick', ( event ) => { 142 | // If the user has not clicked on close button 143 | if ( 'close' !== event.action ) { 144 | event.waitUntil( 145 | // Get all the clients associated with the service worker, of type window, including the uncontrolled ones. 146 | self.clients.matchAll( { type: 'window', includeUncontrolled: true } ) 147 | .then( allClients => { 148 | 149 | console.warn( allClients ); 150 | 151 | allClients.map( client => { 152 | /** 153 | * Check if the client is visible, 154 | * then navigate/move it to the location set in event.notification.data.loc 155 | * Means screen will move to that html element with id you have specified in event.notification.data.loc 156 | */ 157 | if ( 'visible' === client.visibilityState ) { 158 | console.warn( 'Navigating' ); 159 | client.navigate( event.notification.data.loc ); 160 | return; 161 | } 162 | }) 163 | } ) 164 | ) 165 | } 166 | 167 | closeNotification( 'Notification Clicked', event ); 168 | } ); 169 | --------------------------------------------------------------------------------