├── .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 | 
7 |
8 | ## [Sending Notifications in a PWA Demo](https://github.com/imranhsayed/pwa-concepts/tree/send-notification-user-nav) :video_camera:
9 | 
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 | 
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 | 
79 |
80 | ### Installation
81 | 
82 |
83 | ### Cache Storage
84 | 
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 | 
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 | 
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 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Install App
55 |
56 | Add our app to home screen?
57 | No Thanks
58 | Yes, Please!
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 |
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 |
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 |
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 |
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 |
308 | <metadata>
309 |
310 |
313 |
316 |
319 | </metadata>
320 |
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 |
--------------------------------------------------------------------------------