├── config ├── .gitignore ├── config.example.json └── config.js ├── public ├── robots.txt ├── google3915c2aaf77f961f.html ├── favicon.ico ├── img │ ├── lh-logo-128.png │ ├── lh-logo-64.png │ ├── lh_logo_bg.png │ ├── lighthouse-18.png │ ├── lighthouse-36.png │ ├── feed-icon-24px.png │ ├── feed-icon-48px.png │ ├── pwa-directory-preview.png │ ├── GitHub-Mark-Light-24px.png │ └── GitHub-Mark-Light-48px.png ├── favicons │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-70x70.png │ ├── apple-touch-icon.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── android-chrome-36x36.png │ ├── android-chrome-48x48.png │ ├── android-chrome-72x72.png │ ├── android-chrome-96x96.png │ ├── android-chrome-144x144.png │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── android-chrome-384x384.png │ ├── android-chrome-512x512.png │ ├── browserconfig.xml │ └── safari-pinned-tab.svg ├── .well-known │ └── assetlinks.json ├── humans.txt ├── manifest.json └── js │ ├── gulliver-config.js │ ├── util │ └── requestIdleCallback.js │ ├── routing │ ├── transitions.js │ └── route.js │ ├── event-target.js │ ├── search-input.js │ ├── ui │ ├── share-button.js │ ├── signin-button.js │ └── notification-checkbox.js │ ├── analytics.js │ ├── shell.js │ ├── loader.js │ ├── offline-support.js │ ├── gapi.es6.js │ ├── signin.js │ └── chart.js ├── .gcloudignore ├── lighthouse_machine ├── .gitignore ├── .dockerignore ├── package.json ├── README.md ├── entrypoint.sh ├── chromeuser-script.sh ├── .eslintrc.json ├── etc │ └── xvfb ├── app.yaml ├── cpu_monitor.js ├── Dockerfile └── server.js ├── img ├── gulliver-details-one.png ├── gulliver-details-two.png └── gulliver-landing-page.png ├── third_party ├── README.md └── install.sh ├── .eslintignore ├── tsconfig.json ├── .gitignore ├── test ├── app │ ├── manifests │ │ ├── invalid-theme-color.json │ │ ├── icon-url-with-parameter.json │ │ ├── no-icon-array.json │ │ └── inline-image-large-content.json │ ├── lib │ │ ├── promise-sequential.js │ │ ├── manifest.js │ │ ├── color.js │ │ ├── asset-hashing.js │ │ ├── data-fetcher.js │ │ ├── lighthouse.js │ │ ├── favorite-pwa.js │ │ └── images.js │ ├── views │ │ └── helpers │ │ │ └── index.js │ └── controllers │ │ ├── api │ │ ├── lighthouse.js │ │ └── pwa.js │ │ ├── cache.js │ │ └── tasks.js └── client │ └── js │ └── event-target.js ├── views ├── includes │ ├── score.hbs │ ├── chevron_left.hbs │ ├── chevron_right.hbs │ ├── webpagetest.hbs │ ├── pagespeedinsight.hbs │ ├── hourglass.hbs │ ├── icon_log_in.hbs │ ├── icon_log_out.hbs │ ├── icon_search.hbs │ ├── icon_share.hbs │ ├── notifications_off.hbs │ ├── notifications_active.hbs │ ├── lighthouse.hbs │ ├── pwadetails.hbs │ ├── footer.hbs │ ├── metadata.hbs │ ├── head.hbs │ └── header.hbs ├── app │ ├── shell.hbs │ └── offline.hbs ├── 404.hbs └── pwas │ ├── view-rss.hbs │ ├── view.hbs │ ├── form.hbs │ └── list.hbs ├── .travis.yml ├── index.yaml ├── lib ├── event-bus.js ├── promise-sequential.js ├── verify-id-token.js ├── metadata.js ├── manifest.js ├── asset-hashing.js ├── favorite-pwa.js ├── pwa-index.js ├── notifications.js ├── color.js ├── web-performance.js └── tasks.js ├── app.yaml ├── rollup-config ├── gulliver.js ├── pwa-form.js └── lighthouse-chart.js ├── models ├── favorite-pwa.js ├── task.js ├── user.js ├── lighthouse.js ├── manifest.js └── pwa.js ├── .babelrc ├── .eslintrc.json ├── controllers ├── app.js ├── api │ ├── index.js │ ├── lighthouse.js │ └── notifications.js ├── sw.js ├── index.js └── cache.js ├── cron.yaml ├── firebase-messaging-sw.tmpl ├── firebase-messaging-sw-generator.js ├── CONTRIBUTING.md ├── middlewares └── index.js ├── FAQ.md └── package.json /config/.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lighthouse_machine/ 3 | -------------------------------------------------------------------------------- /lighthouse_machine/.gitignore: -------------------------------------------------------------------------------- 1 | outputs 2 | node_modules 3 | -------------------------------------------------------------------------------- /lighthouse_machine/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .git 3 | outputs -------------------------------------------------------------------------------- /public/google3915c2aaf77f961f.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google3915c2aaf77f961f.html -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicon.ico -------------------------------------------------------------------------------- /public/img/lh-logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/lh-logo-128.png -------------------------------------------------------------------------------- /public/img/lh-logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/lh-logo-64.png -------------------------------------------------------------------------------- /public/img/lh_logo_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/lh_logo_bg.png -------------------------------------------------------------------------------- /img/gulliver-details-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/img/gulliver-details-one.png -------------------------------------------------------------------------------- /img/gulliver-details-two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/img/gulliver-details-two.png -------------------------------------------------------------------------------- /public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/favicon.ico -------------------------------------------------------------------------------- /public/img/lighthouse-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/lighthouse-18.png -------------------------------------------------------------------------------- /public/img/lighthouse-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/lighthouse-36.png -------------------------------------------------------------------------------- /img/gulliver-landing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/img/gulliver-landing-page.png -------------------------------------------------------------------------------- /public/img/feed-icon-24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/feed-icon-24px.png -------------------------------------------------------------------------------- /public/img/feed-icon-48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/feed-icon-48px.png -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /public/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /public/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /public/img/pwa-directory-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/pwa-directory-preview.png -------------------------------------------------------------------------------- /public/img/GitHub-Mark-Light-24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/GitHub-Mark-Light-24px.png -------------------------------------------------------------------------------- /public/img/GitHub-Mark-Light-48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/img/GitHub-Mark-Light-48px.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-36x36.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-48x48.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-72x72.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-96x96.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-144x144.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-256x256.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-384x384.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/gulliver/main/public/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /third_party/README.md: -------------------------------------------------------------------------------- 1 | See `./install.sh` for information on where the `*.js` files in this directory 2 | come from, and how to generate them. 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /third_party 3 | /public/js/gulliver.js 4 | /public/js/lighthouse-chart.js 5 | /public/js/pwa-form.js 6 | /lighthouse_machine 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://www.typescriptlang.org/docs/handbook/compiler-options.html 3 | "compilerOptions": { 4 | "target": "es5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | /coverage 5 | .jshintrc 6 | .idea/ 7 | key.json 8 | public/js/gulliver.js 9 | public/js/gulliver.js.map 10 | public/firebase-messaging-sw.js 11 | 12 | -------------------------------------------------------------------------------- /test/app/manifests/invalid-theme-color.json: -------------------------------------------------------------------------------- 1 | { 2 | "start_url": "https://www.terra.com.br/?utm_source=homescreen", 3 | "description": "Manifest with an invalid theme_color", 4 | "theme_color": "not_a_real_color" 5 | } 6 | -------------------------------------------------------------------------------- /views/includes/score.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#if lighthouseScore}} 3 | {{lighthouseScore}} 4 | {{else}} 5 | 6 | {{/if}} 7 | 8 | -------------------------------------------------------------------------------- /views/includes/chevron_left.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /views/includes/chevron_right.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /views/includes/webpagetest.hbs: -------------------------------------------------------------------------------- 1 |
2 |

WebPageTest

3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /views/includes/pagespeedinsight.hbs: -------------------------------------------------------------------------------- 1 |
2 |

PageSpeed Insights

3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /test/app/manifests/icon-url-with-parameter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Test", 3 | "icons": [ 4 | { 5 | "src": "img/launcher-icon.png?v2", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | } 9 | ], 10 | "start_url": "https://www.example.com/?utm_source=homescreen" 11 | } 12 | -------------------------------------------------------------------------------- /public/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #7cc0ff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /views/includes/hourglass.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "relation": ["delegate_permission/common.handle_all_urls"], 3 | "target": { 4 | "namespace": "android_app", 5 | "package_name": "com.appspot.pwa_directory", 6 | "sha256_cert_fingerprints": ["1A:64:23:29:C2:BB:FA:18:45:A3:BE:02:08:DD:B4:8F:51:21:F9:2E:95:75:75:CA:2B:8B:47:75:94:C5:0F:64"]} 7 | }] 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | dist: trusty 4 | node_js: 5 | - "8" 6 | before_script: 7 | - npm install 8 | script: 9 | - npm test 10 | env: 11 | # evade checks in config.js 12 | - CLIENT_ID=placeholder CLIENT_SECRET=placeholder GCLOUD_PROJECT=placeholder CLOUD_BUCKET=placeholder FIREBASE_AUTH=placeholder API_TOKENS="abcdefghijk" 13 | -------------------------------------------------------------------------------- /index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | - kind: Lighthouse 3 | properties: 4 | - name: pwaId 5 | direction: asc 6 | - name: date 7 | direction: desc 8 | - kind: PWA 9 | properties: 10 | - name: installable 11 | - name: lighthouseScore 12 | direction: desc 13 | - kind: PWA 14 | properties: 15 | - name: installable 16 | - name: created 17 | direction: desc 18 | -------------------------------------------------------------------------------- /views/includes/icon_log_in.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/app/manifests/no-icon-array.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Test", 3 | "description": "Manifest with icons that are not in array", 4 | "icons": { 5 | "16": "img/icons/icon16.png", 6 | "32": "img/icons/icon32.png", 7 | "60": "img/icons/icon60.png", 8 | "64": "img/icons/icon64.png", 9 | "90": "img/icons/icon90.png", 10 | "128": "img/icons/icon128.png" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /views/includes/icon_log_out.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /views/includes/icon_search.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | Julian Toledo -- @juliantoledo 7 | Michael Stillwell -- @ithinkihaveacat 8 | Andre Bandarra -- @andreban 9 | Alberto Medina -- @amedina 10 | 11 | # THANKS 12 | 13 | Ade Oshineye -- @ade_oshineye 14 | 15 | # TECHNOLOGY COLOPHON 16 | 17 | CSS3, HTML5, GoogleChrome sw-toolbox 18 | Node, Google App Engine, GoogleChrome Lighthouse 19 | 20 | Source: https://github.com/GoogleChrome/gulliver 21 | -------------------------------------------------------------------------------- /views/includes/icon_share.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /views/includes/notifications_off.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /views/includes/notifications_active.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /lighthouse_machine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lighthouse_machine_server", 3 | "version": "1.0.0", 4 | "description": "A server for the lighthouse machine", 5 | "repository": "https://github.com/GoogleChrome/gulliver/lighthouse_machine", 6 | "author": "Google Inc.", 7 | "contributors": [ 8 | { 9 | "name": "Cedric Bellet", 10 | "email": "cbellet@google.com" 11 | }, 12 | { 13 | "name": "Julian Toledo", 14 | "email": "jtoledo@google.com" 15 | } 16 | ], 17 | "license": "Apache-2.0", 18 | "main": "server.js", 19 | "scripts": { 20 | "start": "node server.js" 21 | }, 22 | "dependencies": { 23 | "express": "^4.16.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /views/includes/lighthouse.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Lighthouse {{> score pwa}}

3 |
4 | 5 |
6 | {{#if pwa.lighthouseScore}} 7 |
8 | 9 | 10 | Lighthouse Report Viewer 11 | 12 |
13 | {{/if}} 14 |
15 | -------------------------------------------------------------------------------- /lighthouse_machine/README.md: -------------------------------------------------------------------------------- 1 | # Lighthouse machine 2 | A Docker image to run [Lighthouse](https://github.com/GoogleChrome/lighthouse) scores on a server 3 | 4 | ## Build the image 5 | ```bash 6 | docker build --no-cache -t lighthouse_machine . 7 | ``` 8 | 9 | ## Run the container 10 | ```bash 11 | # Run a new container 12 | docker run -d -p 8080:8080 --cap-add=SYS_ADMIN lighthouse_machine 13 | ``` 14 | 15 | ## Usage 16 | ```bash 17 | curl -X GET 'http://localhost:8080?format=${format}&url=${url}' 18 | ``` 19 | 20 | where `format`is one of `json`, `html` (see [cli-options](https://github.com/GoogleChrome/lighthouse#cli-options) for more information) 21 | 22 | ## License 23 | See [LICENSE](./LICENSE) for more. 24 | 25 | ## Disclaimer 26 | This is not a Google product. 27 | -------------------------------------------------------------------------------- /lib/event-bus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const EventEmitter = require('events'); 19 | const messageBus = new EventEmitter(); 20 | module.exports = messageBus; 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PWA Directory", 3 | "short_name": "PwaDirectory", 4 | "description": "A Directory of PWAs", 5 | "start_url": "/?utm_source=homescreen", 6 | "icons": [ 7 | { 8 | "src": "\/favicons\/android-chrome-192x192.png", 9 | "sizes": "192x192", 10 | "type": "image\/png" 11 | }, 12 | { 13 | "src": "\/favicons\/android-chrome-512x512.png", 14 | "sizes": "512x512", 15 | "type": "image\/png" 16 | } 17 | ], 18 | "theme_color": "#7cc0ff", 19 | "background_color": "#7cc0ff", 20 | "display": "standalone", 21 | "scope": "/", 22 | "share_target": { 23 | "url_template": "/pwas/add?url={url}" 24 | }, 25 | "//": "Some browsers will use this to enable push notifications.", 26 | "//": "It is the same for all projects, this is not your project's sender ID", 27 | "gcm_sender_id": "103953800507" 28 | } 29 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2016, Google, Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | runtime: nodejs 15 | env: flexible 16 | 17 | instance_class: B4_1G 18 | manual_scaling: 19 | instances: 2 20 | 21 | handlers: 22 | - url: /.* 23 | script: IGNORED 24 | secure: always 25 | 26 | network: 27 | instance_tag: default-service 28 | -------------------------------------------------------------------------------- /rollup-config/gulliver.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import uglify from 'rollup-plugin-uglify'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import commonsjs from 'rollup-plugin-commonjs'; 5 | 6 | export default { 7 | entry: './public/js/gulliver.es6.js', 8 | plugins: [ 9 | babel({exclude: 'node_modules/**'}), 10 | uglify(), 11 | nodeResolve(), 12 | commonsjs() 13 | ], 14 | // Quiet warning: https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined 15 | context: 'window', 16 | targets: [ 17 | { 18 | dest: './public/js/gulliver.js', 19 | // Fixes 'navigator' not defined when using Firebase and strict mode: 20 | // http://stackoverflow.com/questions/31221357/webpack-firebase-disable-parsing-of-firebase 21 | useStrict: false, 22 | format: 'iife', 23 | sourceMap: true 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /rollup-config/pwa-form.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import uglify from 'rollup-plugin-uglify'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import commonsjs from 'rollup-plugin-commonjs'; 5 | 6 | export default { 7 | entry: './public/js/pwa-form.es6.js', 8 | plugins: [ 9 | babel({exclude: 'node_modules/**'}), 10 | uglify(), 11 | nodeResolve(), 12 | commonsjs() 13 | ], 14 | // Quiet warning: https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined 15 | context: 'window', 16 | targets: [ 17 | { 18 | dest: './public/js/pwa-form.js', 19 | // Fixes 'navigator' not defined when using Firebase and strict mode: 20 | // http://stackoverflow.com/questions/31221357/webpack-firebase-disable-parsing-of-firebase 21 | useStrict: false, 22 | format: 'iife', 23 | sourceMap: true 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /rollup-config/lighthouse-chart.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import uglify from 'rollup-plugin-uglify'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import commonsjs from 'rollup-plugin-commonjs'; 5 | 6 | export default { 7 | entry: './public/js/lighthouse-chart.es6.js', 8 | plugins: [ 9 | babel({exclude: 'node_modules/**'}), 10 | uglify(), 11 | nodeResolve(), 12 | commonsjs() 13 | ], 14 | // Quiet warning: https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined 15 | context: 'window', 16 | targets: [ 17 | { 18 | dest: './public/js/lighthouse-chart.js', 19 | // Fixes 'navigator' not defined when using Firebase and strict mode: 20 | // http://stackoverflow.com/questions/31221357/webpack-firebase-disable-parsing-of-firebase 21 | useStrict: false, 22 | format: 'iife', 23 | sourceMap: true 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /lighthouse_machine/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2016-2017, Google, Inc. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | /etc/init.d/dbus start 17 | /etc/init.d/xvfb start 18 | sleep 1s 19 | 20 | export DISPLAY=:1 21 | TMP_PROFILE_DIR=$(mktemp -d -t lighthouse.XXXXXXXXXX) 22 | 23 | su chromeuser 24 | source /chromeuser-script.sh 25 | sleep 3s 26 | 27 | node /server.js 28 | -------------------------------------------------------------------------------- /lighthouse_machine/chromeuser-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2016-2017, Google, Inc. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | sudo chown -R chromeuser:chromeuser $TMP_PROFILE_DIR 17 | export DISPLAY=:0 18 | Xvfb :0 -screen 0 1024x768x24 & 19 | nohup google-chrome --no-first-run --disable-gpu --no-sandbox --user-data-dir=$TMP_PROFILE_DIR --remote-debugging-port=9222 'about:blank' & 20 | -------------------------------------------------------------------------------- /models/favorite-pwa.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | /** 19 | * Favorite Pwa for a user 20 | */ 21 | class FavoritePwa { 22 | constructor(pwaId, userId) { 23 | this.id = pwaId + '-' + userId; 24 | this.pwaId = pwaId; 25 | this.userId = userId; 26 | } 27 | } 28 | 29 | module.exports = FavoritePwa; 30 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | { 16 | "env": { 17 | "test": { 18 | "presets": ["es2015"] 19 | }, 20 | "default": { 21 | "presets": [ 22 | [ 23 | "es2015", 24 | { 25 | "modules": false 26 | } 27 | ] 28 | ], 29 | "plugins": ["external-helpers"] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /models/task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | class Task { 19 | constructor(pwaId, modulePath, functionName, retries) { 20 | this.pwaId = pwaId; 21 | this.modulePath = modulePath; 22 | this.functionName = functionName; 23 | this.retries = retries; 24 | this.created = new Date(); 25 | } 26 | } 27 | 28 | module.exports = Task; 29 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const crypto = require('crypto'); 19 | 20 | /** 21 | * User from google-auth-library-nodejs client 22 | */ 23 | class User { 24 | constructor(googleLogin) { 25 | this.id = crypto.createHash('sha1').update(googleLogin.getPayload().sub).digest('hex'); 26 | } 27 | } 28 | 29 | module.exports = User; 30 | -------------------------------------------------------------------------------- /lighthouse_machine/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "google", 3 | "installedESLint": true, 4 | // http://eslint.org/docs/rules/ 5 | "rules": { 6 | "max-len": [2, 100, { 7 | "ignoreComments": true, 8 | "ignoreUrls": true, 9 | "tabWidth": 2 10 | }], 11 | "no-implicit-coercion": [2, { 12 | "boolean": false, 13 | "number": true, 14 | "string": true 15 | }], 16 | "no-unused-expressions": [2, { 17 | "allowShortCircuit": true, 18 | "allowTernary": false 19 | }], 20 | "no-unused-vars": [2, { 21 | "vars": "all", 22 | "args": "after-used", 23 | "argsIgnorePattern": "(^reject$|^_$)", 24 | "varsIgnorePattern": "(^_$)" 25 | }], 26 | "quotes": [2, "single"], 27 | "require-jsdoc": 0, 28 | "valid-jsdoc": 0, 29 | "prefer-arrow-callback": 1, 30 | "no-var": 1 31 | }, 32 | // http://eslint.org/docs/user-guide/configuring#specifying-environments 33 | "env": { 34 | "node": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "google", 3 | // http://eslint.org/docs/rules/ 4 | "rules": { 5 | "max-len": [2, 100, { 6 | "ignoreComments": true, 7 | "ignoreUrls": true, 8 | "tabWidth": 2 9 | }], 10 | "no-implicit-coercion": [2, { 11 | "boolean": false, 12 | "number": true, 13 | "string": true 14 | }], 15 | "no-unused-expressions": [2, { 16 | "allowShortCircuit": true, 17 | "allowTernary": false 18 | }], 19 | "no-unused-vars": [2, { 20 | "vars": "all", 21 | "args": "after-used", 22 | "argsIgnorePattern": "(^reject$|^_$)", 23 | "varsIgnorePattern": "(^_$)" 24 | }], 25 | "quotes": [2, "single"], 26 | "require-jsdoc": 0, 27 | "valid-jsdoc": 0, 28 | "prefer-arrow-callback": 1, 29 | "no-var": 1 30 | }, 31 | // http://eslint.org/docs/user-guide/configuring#specifying-environments 32 | "env": { 33 | "node": true 34 | }, 35 | "parserOptions": { 36 | "ecmaVersion": 2017 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /views/app/shell.hbs: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | {{> head}} 18 | 19 | 20 | {{> header}} 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | {{> footer}} 29 | 30 | 31 | -------------------------------------------------------------------------------- /controllers/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const express = require('express'); 19 | const router = express.Router(); // eslint-disable-line new-cap 20 | 21 | router.get('/shell', (req, res) => { 22 | res.render('app/shell.hbs'); 23 | }); 24 | 25 | router.get('/offline', (req, res, next) => { // eslint-disable-line no-unused-vars 26 | res.render('app/offline.hbs'); 27 | }); 28 | 29 | module.exports = router; 30 | -------------------------------------------------------------------------------- /views/app/offline.hbs: -------------------------------------------------------------------------------- 1 | {{!-- 2 | Copyright 2015-2016, Google, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | --}} 16 | 17 | 18 | 19 | Gulliver - PWA Directory - Offline 20 | {{> head}} 21 | 22 | 23 | {{> header}} 24 |
Offline
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{> footer}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/js/gulliver-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /* eslint-env browser */ 17 | 18 | export default class Config { 19 | constructor(element) { 20 | if (!element) { 21 | console.log('%cConfig not found', 'color:red'); 22 | return; 23 | } 24 | const configJSON = JSON.parse(element.innerHTML); 25 | Object.assign(this, configJSON); 26 | } 27 | 28 | static from(element) { 29 | return new Config(element); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /views/includes/pwadetails.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{#if pwa.iconUrl128}} 5 | 6 | {{else}} 7 | 8 | 9 | {{firstLetter pwa.name}} 10 | 11 | {{/if}} 12 |
13 |

{{pwa.displayName}}

14 | {{pwa.absoluteStartUrl}} 15 |
{{pwa.description}}
16 |
{{#if pwa.created}}Added {{moment pwa.created}}, Updated {{moment pwa.updated}}{{/if}}
17 |
18 | -------------------------------------------------------------------------------- /config/config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "See README.md for more information about what to put here", 3 | "GCLOUD_PROJECT": "run `gcloud config get-value project`", 4 | "CLOUD_BUCKET": "see https://console.cloud.google.com/storage/browser?project=$GCLOUD_PROJECT", 5 | "CLIENT_ID": "see https://console.cloud.google.com/apis/credentials?project=$GCLOUD_PROJECT", 6 | "CLIENT_SECRET": "see https://console.cloud.google.com/apis/credentials?project=$GCLOUD_PROJECT", 7 | "WEBPERFORMANCE_SERVER": "your Web Performance server URL (optional)", 8 | "WEBPERFORMANCE_SERVER_API_KEY": "your Key for the Web Performance Service", 9 | "GOOGLE_ANALYTICS": "your Google Analytics tracking code (optional)", 10 | "CANONICAL_ROOT": "your website root address. Can be http://localhost:8080 in development", 11 | "FIREBASE_AUTH": "the 'Server key' (optional); see https://console.firebase.google.com/project/$FIREBASE_PROJECT/settings/cloudmessaging", 12 | "FIREBASE_MSG_SENDER_ID": "the 'Sender ID' (optional); see https://console.firebase.google.com/project/$FIREBASE_PROJECT/settings/cloudmessaging" 13 | } 14 | -------------------------------------------------------------------------------- /views/404.hbs: -------------------------------------------------------------------------------- 1 | 13 | 14 | {{#unless contentOnly}} 15 | 16 | 17 | 18 | {{> head}} 19 | 20 | 21 | {{> header}} 22 |
23 |
24 | {{/unless}} 25 |
Page Not Found
26 | {{#unless contentOnly}} 27 |
28 |
29 |
30 |
31 | {{> footer}} 32 | 33 | 34 | {{/unless}} 35 | -------------------------------------------------------------------------------- /cron.yaml: -------------------------------------------------------------------------------- 1 | cron: 2 | - description: (Node) Daily PWA info update job 3 | url: /tasks/cron 4 | schedule: every day 13:00 5 | 6 | - description: (Node) Execute PWA update tasks 7 | url: /tasks/execute?tasks=30 8 | schedule: every 1 minutes 9 | 10 | - description: (Node) Update unscored PWAs 11 | url: /tasks/updateunscored 12 | schedule: every 1 hours 13 | 14 | - description: Update unscored PWAs 15 | url: /taskcreator/task?unscored=true 16 | schedule: every 1 hours 17 | target: web-performance 18 | 19 | - description: UpdateManifestTask 20 | url: /taskcreator/task/UpdateManifestTask 21 | schedule: every day 16:00 22 | target: web-performance 23 | 24 | - description: UpdateIconTask 25 | url: /taskcreator/task/UpdateIconTask 26 | schedule: every monday 01:00 27 | target: web-performance 28 | 29 | - description: PageSpeedReportTask 30 | url: /taskcreator/task/PageSpeedReportTask 31 | schedule: every friday 01:00 32 | target: web-performance 33 | 34 | - description: WebPageTestReportTask 35 | url: /taskcreator/task/WebPageTestReportTask 36 | schedule: every sunday 01:00 37 | target: web-performance 38 | -------------------------------------------------------------------------------- /firebase-messaging-sw.tmpl: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /* eslint-env serviceworker, browser */ 17 | /* global firebase */ 18 | importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-app.js'); 19 | importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js'); 20 | 21 | firebase.initializeApp({ 22 | messagingSenderId: '<%= firebaseMsgSenderId %>' 23 | }); 24 | const messaging = firebase.messaging(); 25 | messaging.setBackgroundMessageHandler(_ => { 26 | return self.registration.showNotification(); 27 | }); 28 | -------------------------------------------------------------------------------- /third_party/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Downloads and patches the two files from lighthouse and devtools that we need 4 | # to validate manifests. 5 | # 6 | # (These files are part of lighthouse, but depending on lighthouse would haul in 7 | # multiple MBs of dependencies. So, we'll just dump the files here.) 8 | 9 | curl -sO https://raw.githubusercontent.com/GoogleChrome/lighthouse/master/lighthouse-core/lib/manifest-parser.js 10 | curl -sO https://raw.githubusercontent.com/ChromeDevTools/devtools-frontend/master/front_end/common/Color.js 11 | 12 | # Generate patch via `git diff manifest-parser.js Color.js` 13 | git apply - << END 14 | diff --git i/third_party/manifest-parser.js w/third_party/manifest-parser.js 15 | index 5ac9d72..b9e7ead 100644 16 | --- i/third_party/manifest-parser.js 17 | +++ w/third_party/manifest-parser.js 18 | @@ -17,7 +17,10 @@ 19 | 'use strict'; 20 | 21 | const url = require('url'); 22 | -const validateColor = require('./web-inspector').Color.parse; 23 | + 24 | +global.Common = {}; // the global is unfortunate, but necessary 25 | +require('./Color.js'); 26 | +const validateColor = global.Common.Color.parse; 27 | 28 | const ALLOWED_DISPLAY_VALUES = [ 29 | 'fullscreen', 30 | END 31 | -------------------------------------------------------------------------------- /lib/promise-sequential.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | /** Execute a list of Promise return functions serially 19 | * @param {list} a list of promise returning functions to execute serially 20 | * @return {Promise} the result of the last promise in the list 21 | * Example: 22 | * promiseSequential.all([ 23 | * _ => this.function1(result), 24 | * result => this.function2(result), 25 | * result => this.function3(result) 26 | * ]); 27 | */ 28 | exports.all = function(promiseList) { 29 | return promiseList.reduce((promiseFn, fn) => { 30 | return promiseFn.then(fn); 31 | }, Promise.resolve()); 32 | }; 33 | -------------------------------------------------------------------------------- /controllers/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const express = require('express'); 19 | const router = express.Router(); // eslint-disable-line new-cap 20 | 21 | // Includes APIs for Lighthouse (/api/lighthouse) 22 | router.use('/lighthouse', require('./lighthouse')); 23 | 24 | // Includes APIs for Notifications (/api/notifications) 25 | router.use('/notifications', require('./notifications')); 26 | 27 | // Includes APIs for FavoritePwas (/api/favoritepwa) 28 | router.use('/favorite-pwa', require('./favorite-pwa')); 29 | 30 | // Includes APIs for PWAs (/api/pwa) 31 | router.use('/pwa', require('./pwa')); 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /public/js/util/requestIdleCallback.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | /* eslint-env browser */ 18 | 19 | /* 20 | * @see https://developers.google.com/web/updates/2015/08/using-requestidlecallback 21 | */ 22 | window.requestIdleCallback = window.requestIdleCallback || 23 | (cb => { 24 | return setTimeout(_ => { 25 | let start = Date.now(); 26 | cb({ 27 | didTimeout: false, 28 | timeRemaining: _ => { 29 | return Math.max(0, 50 - (Date.now() - start)); 30 | } 31 | }); 32 | }, 1); 33 | }); 34 | 35 | window.cancelIdleCallback = window.cancelIdleCallback || 36 | (id => { 37 | clearTimeout(id); 38 | }); 39 | -------------------------------------------------------------------------------- /controllers/sw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const express = require('express'); 19 | const router = express.Router(); // eslint-disable-line new-cap 20 | const asset = require('../lib/asset-hashing').asset; 21 | 22 | const ASSETS = JSON.stringify([ 23 | '/css/style.css', 24 | '/js/gulliver.js' 25 | ].map(assetPath => asset.encode(assetPath))); 26 | 27 | const ASSETS_JS = `const ASSETS = ${ASSETS};`; 28 | 29 | router.get('/sw-assets-precache.js', (req, res) => { 30 | res.setHeader('Content-Type', 'application/javascript'); 31 | res.setHeader('Cache-Control', 'no-cache, max-age=0'); 32 | res.send(ASSETS_JS); 33 | }); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /lib/verify-id-token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const config = require('../config/config'); 19 | const CLIENT_ID = config.get('CLIENT_ID'); 20 | const CLIENT_SECRET = config.get('CLIENT_SECRET'); 21 | 22 | /** 23 | * @param {string} idToken 24 | * @return {Promise} 25 | */ 26 | exports.verifyIdToken = function(idToken) { 27 | const {OAuth2Client} = require('google-auth-library'); 28 | const client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET); 29 | return new Promise((resolve, reject) => { 30 | client.verifyIdToken({idToken, CLIENT_ID}, (err, googleLogin) => { 31 | if (err) { 32 | reject(err); 33 | } 34 | resolve(googleLogin); 35 | }); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /lighthouse_machine/etc/xvfb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2016-2017, Google, Inc. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | XVFB_OUTPUT=/tmp/Xvfb.out 17 | XVFB=/usr/bin/X11/Xvfb 18 | XVFB_OPTIONS=":1 -screen 0 1024x768x24 -fbdir /var/run" 19 | 20 | start() { 21 | echo -n "Starting : X Virtual Frame Buffer " 22 | $XVFB $XVFB_OPTIONS >>$XVFB_OUTPUT 2>&1& 23 | RETVAL=$? 24 | echo 25 | return $RETVAL 26 | } 27 | 28 | stop() { 29 | echo -n "Shutting down : X Virtual Frame Buffer" 30 | echo 31 | pkill Xvfb 32 | echo 33 | return 0 34 | } 35 | 36 | case "$1" in 37 | start) 38 | start 39 | ;; 40 | stop) 41 | stop 42 | ;; 43 | status) 44 | status xvfb 45 | ;; 46 | restart) 47 | stop 48 | start 49 | ;; 50 | 51 | *) 52 | echo "Usage: xvfb {start|stop|status|restart}" 53 | exit 1 54 | ;; 55 | esac 56 | exit $? 57 | -------------------------------------------------------------------------------- /public/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /firebase-messaging-sw-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const fs = require('fs'); 19 | const template = require('lodash.template'); 20 | const config = require('./config/config'); 21 | 22 | const firebaseMsgSenderId = config.get('FIREBASE_MSG_SENDER_ID'); 23 | 24 | fs.readFile('./firebase-messaging-sw.tmpl', 'utf8', (error, data) => { 25 | if (error) { 26 | console.error('Error reading template: ', error); 27 | return; 28 | } 29 | 30 | const firebaseMessagingSwFileContent = template(data)({ 31 | firebaseMsgSenderId: firebaseMsgSenderId 32 | }); 33 | 34 | fs.writeFile('./public/firebase-messaging-sw.js', firebaseMessagingSwFileContent, err => { 35 | if (err) { 36 | console.log('Error Writing firebase-messaging-sw: ', err); 37 | } 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /views/pwas/view-rss.hbs: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | {{#if pwa.iconUrl128}} 15 | 16 | {{/if}} 17 | 18 |

{{pwa.displayName}}

19 |
{{pwa.description}}
20 |
21 | 22 | 23 | 24 | Open in PWA Directory 25 | 26 | -------------------------------------------------------------------------------- /views/pwas/view.hbs: -------------------------------------------------------------------------------- 1 | 13 | 14 | {{#unless contentOnly}} 15 | 16 | 17 | 18 | {{> head}} 19 | 20 | 21 | {{> header}} 22 |
23 |
24 | {{/unless}} 25 | {{> pwadetails pwa=pwa}} 26 | {{> lighthouse lighthouse=lighthouse}} 27 | {{> webpagetest}} 28 | {{> pagespeedinsight}} 29 | {{#if pwa.manifest}} 30 |
31 |

MANIFEST

32 |
{{{highlightedJson rawManifestJson}}}
33 |
34 | {{/if}} 35 | {{#unless contentOnly}} 36 |
37 |
38 |
39 |
40 | {{> footer}} 41 | 42 | 43 | {{/unless}} 44 | -------------------------------------------------------------------------------- /lighthouse_machine/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017, Google, Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | runtime: custom 15 | env: flex 16 | service: lighthouse-machine 17 | automatic_scaling: 18 | min_num_instances: 2 19 | max_num_instances: 6 20 | cool_down_period_sec: 60 21 | cpu_utilization: 22 | target_utilization: 0.6 23 | 24 | resources: 25 | cpu: 1 26 | memory_gb: 4 27 | disk_size_gb: 10 28 | 29 | handlers: 30 | - url: /.* 31 | script: IGNORED 32 | secure: always 33 | 34 | liveness_check: 35 | path: '/_ah/health' 36 | check_interval_sec: 30 37 | timeout_sec: 4 38 | failure_threshold: 3 39 | success_threshold: 2 40 | initial_delay_sec: 60 41 | 42 | readiness_check: 43 | path: '/_ah/busy' 44 | check_interval_sec: 3 45 | timeout_sec: 2 46 | failure_threshold: 1 47 | success_threshold: 1 48 | app_start_timeout_sec: 300 49 | 50 | network: 51 | instance_tag: lighthouse-machine 52 | -------------------------------------------------------------------------------- /lib/metadata.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | /** 19 | * Generates the default metadata from a http request 20 | */ 21 | module.exports.fromRequest = function(req, newUrl) { 22 | const host = req.get('host'); 23 | const url = newUrl || req.protocol + '://' + host + req.originalUrl; 24 | const timestamp = new Date().toISOString(); 25 | const logo = req.protocol + '://' + host + '/favicons/android-chrome-512x512.png'; 26 | const leader = req.protocol + '://' + host + '/img/pwa-directory-preview.png'; 27 | const metadata = { 28 | url: url, 29 | host: host, 30 | datePublished: timestamp, 31 | dateModified: timestamp, 32 | logo: logo, 33 | logoWidth: '512', 34 | logoHeight: '512', 35 | leader: leader, 36 | leaderWidth: '2008', 37 | leaderHeight: '1386' 38 | }; 39 | return metadata; 40 | }; 41 | -------------------------------------------------------------------------------- /public/js/routing/transitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /* eslint-env browser */ 17 | import Loader from '../loader'; 18 | 19 | export class FadeInOutTransitionStrategy { 20 | transitionIn(container) { 21 | container.classList.remove('transition'); 22 | } 23 | 24 | transitionOut(container) { 25 | container.classList.add('transition'); 26 | } 27 | } 28 | 29 | export class LoaderTransitionStrategy { 30 | constructor(window) { 31 | this._window = window; 32 | const loaderDiv = window.document.querySelector('.page-loader'); 33 | this._loader = new Loader(loaderDiv); 34 | } 35 | 36 | transitionIn(container) { 37 | container.classList.remove('transition'); 38 | this._loader.hide(); 39 | } 40 | 41 | transitionOut(container) { 42 | container.classList.add('transition'); 43 | this._loader.show(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement] 6 | (https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | Before you start working on a larger contribution, you should get in touch with 15 | us first through the issue tracker with your idea so that we can help out and 16 | possibly guide you. Coordinating up front makes it much easier to avoid 17 | frustration later on. 18 | 19 | ### Code reviews 20 | All submissions, including submissions by project members, require review. We 21 | use Github pull requests for this purpose. 22 | 23 | ### The small print 24 | Contributions made by corporations are covered by a different agreement than 25 | the one above, the 26 | [Software Grant and Corporate Contributor License Agreement] 27 | (https://cla.developers.google.com/about/google-corporate). 28 | -------------------------------------------------------------------------------- /public/js/event-target.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | export default class EventTarget { 17 | constructor() { 18 | this._listeners = new Map(); 19 | } 20 | 21 | addEventListener(type, callback) { 22 | let typeListeners = this._listeners.get(type); 23 | if (!typeListeners) { 24 | typeListeners = new Set(); 25 | this._listeners.set(type, typeListeners); 26 | } 27 | typeListeners.add(callback); 28 | } 29 | 30 | removeEventListener(type, callback) { 31 | const typeListeners = this._listeners.get(type); 32 | if (!typeListeners) { 33 | return; 34 | } 35 | typeListeners.delete(callback); 36 | } 37 | 38 | getEventListeners(type) { 39 | return this._listeners.get(type); 40 | } 41 | 42 | dispatchEvent(event) { 43 | if (!event.type) { 44 | return; 45 | } 46 | 47 | const typeListeners = this._listeners.get(event.type); 48 | if (!typeListeners) { 49 | return; 50 | } 51 | 52 | typeListeners.forEach(callback => callback(event)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/js/search-input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /* 17 | * Generate gulliver.js from this file via `npm prestart`. (`npm start` will run 18 | * `prestart` automatically.) 19 | */ 20 | 21 | /* eslint-env browser */ 22 | 23 | class SearchButton { 24 | /** 25 | * Setup/configure search button 26 | */ 27 | setupSearchElements(router) { 28 | const eventHandler = event => { 29 | event.preventDefault(); 30 | const searchValue = document.querySelector('#search-input').value; 31 | if (searchValue.length === 0) { 32 | router.navigate('/'); 33 | } else { 34 | const urlParams = new URLSearchParams(window.location.search); 35 | // Only navigate if the search query changes 36 | if (searchValue !== urlParams.get('query')) { 37 | router.navigate('/pwas/search?query=' + searchValue); 38 | } 39 | document.querySelector('#search-input').blur(); 40 | } 41 | }; 42 | document.querySelector('#search').addEventListener('submit', eventHandler); 43 | } 44 | } 45 | 46 | export default new SearchButton(); 47 | -------------------------------------------------------------------------------- /test/client/js/event-target.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /* global describe it beforeEach afterEach */ 17 | 'use strict'; 18 | import EventTarget from '../../../public/js/event-target'; 19 | const assert = require('assert'); 20 | const simpleMock = require('simple-mock'); 21 | 22 | describe('js.event-target', () => { 23 | let eventTarget; 24 | const callbackA = simpleMock.spy(() => {}); 25 | 26 | beforeEach(() => { 27 | eventTarget = new EventTarget(); 28 | }); 29 | 30 | afterEach(() => { 31 | simpleMock.restore(); 32 | callbackA.reset(); 33 | }); 34 | 35 | it('Fires the correct callback', () => { 36 | eventTarget.addEventListener('event-a', callbackA); 37 | eventTarget.dispatchEvent({type: 'event-a'}); 38 | assert.equal(callbackA.callCount, 1); 39 | }); 40 | 41 | it('Does not invoke a removed callback', () => { 42 | eventTarget.addEventListener('event-a', callbackA); 43 | eventTarget.removeEventListener('event-a', callbackA); 44 | eventTarget.dispatchEvent({type: 'event-a'}); 45 | assert.equal(callbackA.callCount, 0); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /views/includes/footer.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Copyright 2015-2016, Google, Inc. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. --}} 13 | 14 | 15 | 16 | 17 |