├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── badge.png ├── favicon.ico ├── icon.png ├── index.css ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── media-session.jpg ├── offline.html ├── placeholder.png ├── robots.txt ├── share-target.jpg ├── shortcuts.jpg ├── sw.js ├── taj-mahal-1.jpg ├── taj-mahal-2.jpg ├── taj-mahal-3.jpg └── taj-mahal-4.jpg ├── server ├── .gitignore ├── config │ └── db.js ├── index.js ├── models │ └── subscription.js ├── package-lock.json └── package.json ├── src ├── App.js ├── components │ ├── Back.jsx │ ├── Header.jsx │ ├── LazyImage.jsx │ ├── ReactIntersectionObserver.jsx │ └── RouteLoader.jsx ├── helpers │ └── index.js ├── images │ ├── logo192.png │ └── logo512.png ├── index.js ├── lib │ └── NativeIntersectionObserver.js ├── logo.svg ├── pages │ ├── apis │ │ ├── AmbientLightSensor.jsx │ │ ├── Battery.jsx │ │ ├── Call.jsx │ │ ├── Clipboard.jsx │ │ ├── Contacts.jsx │ │ ├── DeviceMemory.jsx │ │ ├── FileRead.jsx │ │ ├── FileWrite.jsx │ │ ├── FullScreen.jsx │ │ ├── GeoLocation.jsx │ │ ├── IdleDetector.jsx │ │ ├── Install.jsx │ │ ├── IntersectionObserver.jsx │ │ ├── MediaSession.jsx │ │ ├── NetworkInformation.jsx │ │ ├── NoSleep.jsx │ │ ├── Notifications.jsx │ │ ├── OnlineState.jsx │ │ ├── PageVisibility.jsx │ │ ├── PaymentRequest.jsx │ │ ├── PushNotifications.jsx │ │ ├── ScreenMedia.jsx │ │ ├── Share.jsx │ │ ├── ShareTarget.jsx │ │ ├── StorageQuota.jsx │ │ ├── TextFragments.jsx │ │ ├── UserMedia.jsx │ │ ├── Virate.jsx │ │ └── WakeLock.jsx │ └── others │ │ ├── About.jsx │ │ ├── Home.jsx │ │ └── Profile.jsx └── serviceWorker.js └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [yTakkar] 4 | patreon: yTakkar 5 | open_collective: faiyaz-shaikh 6 | ko_fi: V7zEUYj 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['paypal.me/faiyazTakkar'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Faiyaz Shaikh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web: https://awesome-web-apis.surge.sh/ 2 | **Note: Please access the app on a mobile browser and make sure it's modern. I'd suggest using Chrome Canary.** 3 | 4 | # API: https://awesome-web-apis-server.herokuapp.com/ 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awesome-web-apis", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "browser-fs-access": "^0.13.1", 10 | "js-cookie": "^2.2.1", 11 | "nosleep.js": "^0.11.0", 12 | "react": "^16.13.1", 13 | "react-dom": "^16.13.1", 14 | "react-router": "^5.1.2", 15 | "react-router-dom": "^5.1.2", 16 | "react-scripts": "3.4.1" 17 | }, 18 | "scripts": { 19 | "start": "NODE_ENV=development react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "rename": "cp ./build/index.html ./build/200.html", 24 | "web:deploy": "NODE_ENV=production npm run build && npm run rename && surge -p build -d https://awesome-web-apis.surge.sh", 25 | "server:deploy": "git subtree push --prefix server heroku master", 26 | "deploy": "npm run web:deploy && npm run server:deploy" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/badge.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/favicon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/icon.png -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | background: #fbfbfb; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | 16 | h4 { 17 | margin: 0; 18 | } 19 | 20 | .api-links a { 21 | display: block; 22 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 30 | Awesome Web APIs 31 | 32 | 33 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Awesome Web APIs", 3 | "name": "Awesome Web APIs", 4 | "icons": [ 5 | { 6 | "src": "logo192.png", 7 | "type": "image/png", 8 | "sizes": "192x192", 9 | "purpose": "any" 10 | }, 11 | { 12 | "src": "logo512.png", 13 | "type": "image/png", 14 | "sizes": "512x512", 15 | "purpose": "any" 16 | } 17 | ], 18 | "start_url": ".", 19 | "display": "standalone", 20 | "theme_color": "#000000", 21 | "background_color": "#ffffff", 22 | "orientation": "portrait-primary", 23 | "shortcuts": [ 24 | { 25 | "name": "Profile", 26 | "short_name": "Profile", 27 | "url": "/profile", 28 | "icons": [{"src": "logo192.png", "sizes": "192x192"}] 29 | }, 30 | { 31 | "name": "About", 32 | "short_name": "About", 33 | "url": "/about", 34 | "icons": [{"src": "logo192.png", "sizes": "192x192"}] 35 | } 36 | ], 37 | "share_target": { 38 | "action": "/api/share-target", 39 | "params": { 40 | "title": "title", 41 | "text": "description", 42 | "url": "url" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/media-session.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/media-session.jpg -------------------------------------------------------------------------------- /public/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 59 | 60 | 61 | 62 |
63 | 64 |
65 | offline 67 |
68 |
You're Offline
69 | 70 |
No Internet connection found. Check your connection or try again
71 | 72 | 73 |
74 | 75 | -------------------------------------------------------------------------------- /public/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/placeholder.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/share-target.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/share-target.jpg -------------------------------------------------------------------------------- /public/shortcuts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/shortcuts.jpg -------------------------------------------------------------------------------- /public/sw.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | /* global workbox */ 3 | 4 | // eslint-disable-next-line no-undef 5 | importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.2/workbox-sw.js'); 6 | 7 | workbox.core.setCacheNameDetails({ 8 | prefix: 'cra', 9 | suffix: 'v1', 10 | }); 11 | 12 | workbox.routing.registerRoute( 13 | /\.(?:png|jpg|jpeg|svg|gif)$/, 14 | new workbox.strategies.CacheFirst({ 15 | // Use a custom cache name. 16 | cacheName: 'image-cache', 17 | plugins: [ 18 | new workbox.expiration.Plugin({ 19 | // Cache only 20 images. 20 | maxEntries: 20, 21 | // Cache for a maximum of a week. 22 | maxAgeSeconds: 7 * 24 * 60 * 60, 23 | }) 24 | ], 25 | }) 26 | ) 27 | 28 | workbox.routing.registerRoute( 29 | /.*\.css$/, 30 | new workbox.strategies.StaleWhileRevalidate({ 31 | cacheName: 'css-cache', 32 | plugins: [ 33 | new workbox.expiration.Plugin({ 34 | maxEntries: 10, 35 | maxAgeSeconds: 30 * 24 * 60 * 60, 36 | }) 37 | ] 38 | }) 39 | ) 40 | workbox.routing.registerRoute( 41 | /(?:static\/.*\.js|\/manifest\.json)$/, 42 | new workbox.strategies.StaleWhileRevalidate({ 43 | cacheName: 'js-cache', 44 | plugins: [ 45 | new workbox.expiration.Plugin({ 46 | maxEntries: 50, 47 | maxAgeSeconds: 30 * 24 * 60 * 60, 48 | }) 49 | ] 50 | }) 51 | ) 52 | 53 | const OFFLINE_CACHE_NAME = 'offline-html'; 54 | const OFFLINE_HTML_URL = '/offline.html'; 55 | 56 | self.addEventListener('install', async (event) => { 57 | event.waitUntil( 58 | caches.open(OFFLINE_CACHE_NAME) 59 | .then((cache) => cache.add(OFFLINE_HTML_URL)) 60 | ); 61 | }); 62 | 63 | workbox.navigationPreload.enable(); 64 | 65 | const networkOnly = new workbox.strategies.NetworkOnly(); 66 | const navigationHandler = async (params) => { 67 | try { 68 | // Attempt a network request. 69 | return await networkOnly.handle(params); 70 | } catch (error) { 71 | // If it fails, return the cached HTML. 72 | return caches.match(OFFLINE_HTML_URL, { 73 | cacheName: OFFLINE_CACHE_NAME, 74 | }); 75 | } 76 | }; 77 | 78 | // Register this strategy to handle all navigations. 79 | workbox.routing.registerRoute( 80 | new workbox.routing.NavigationRoute(navigationHandler) 81 | ); 82 | 83 | self.addEventListener('push', event => { 84 | console.log('[Service Worker] Push Received.'); 85 | console.log(`[Service Worker] Push had this data: "${event.data.text()}"`); 86 | 87 | const title = 'Push notification'; 88 | const options = { 89 | body: 'Hey, how are you doing?', 90 | icon: 'images/logo512.png', 91 | badge: 'images/logo192.png', 92 | vibrate: [500,110,500,110,450,110,200,110,170,40,450,110,200,110,170,40,500], 93 | requireInteraction: true, 94 | silent: false, 95 | actions: [ 96 | { 97 | action: 'action-1', 98 | title: 'Action 1', 99 | }, 100 | { 101 | action: 'action-2', 102 | title: 'Action 2', 103 | } 104 | ] 105 | }; 106 | 107 | event.waitUntil( 108 | self.registration.showNotification(title, options) 109 | ); 110 | }); 111 | 112 | self.addEventListener('notificationclick', e => { 113 | if (!e.action) { 114 | self.clients.matchAll().then(clientList => { 115 | console.log(clientList) 116 | 117 | if (clientList.length > 0) { 118 | return clientList[0].focus(); 119 | } 120 | }) 121 | return; 122 | } 123 | 124 | switch (e.action) { 125 | case 'action-1': 126 | console.log('Action 1 clicked') 127 | self.clients.openWindow('https://www.google.com/') 128 | break; 129 | 130 | default: 131 | console.log('Unknown action') 132 | self.clients.openWindow('https://www.espncricinfo.com/') 133 | break; 134 | } 135 | }) 136 | -------------------------------------------------------------------------------- /public/taj-mahal-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/taj-mahal-1.jpg -------------------------------------------------------------------------------- /public/taj-mahal-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/taj-mahal-2.jpg -------------------------------------------------------------------------------- /public/taj-mahal-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/taj-mahal-3.jpg -------------------------------------------------------------------------------- /public/taj-mahal-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/public/taj-mahal-4.jpg -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /server/config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const db = mongoose.connect('mongodb://admin:adminPassword786@ds143262.mlab.com:43262/push-notifications', { 3 | useNewUrlParser: true, 4 | useUnifiedTopology: true 5 | }) 6 | 7 | module.exports = db; -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const cors = require('cors') 3 | const bodyParser = require('body-parser') 4 | const app = express() 5 | const subscriptions = require('./models/subscription') 6 | const webPush = require('web-push') 7 | 8 | require('./config/db') 9 | 10 | app.use(cors()) 11 | app.use(bodyParser.json()) 12 | app.use(bodyParser.urlencoded({ 13 | extended: false 14 | })) 15 | 16 | const triggerPushMsg = function(subscription, dataToSend) { 17 | return webPush.sendNotification(JSON.parse(subscription.subscription), dataToSend) 18 | .catch((err) => { 19 | if (err.statusCode === 404 || err.statusCode === 410) { 20 | console.log('Subscription has expired or is no longer valid: ', err); 21 | return subscriptions.findByIdAndDelete(subscription._id); 22 | } else { 23 | throw err; 24 | } 25 | }); 26 | }; 27 | 28 | app.get('/', (req, res) => res.send('Hello from the server :)')) 29 | 30 | app.post('/api/save-subscription', async (req, res) => { 31 | subscriptions.create({ subscription: JSON.stringify(req.body) }) 32 | .then(resp => { 33 | res.setHeader('Content-Type', 'application/json'); 34 | res.send({ 35 | data: { 36 | success: true, 37 | id: resp._id 38 | } 39 | }); 40 | }) 41 | .catch(e => { 42 | res.status(500); 43 | res.setHeader('Content-Type', 'application/json'); 44 | res.send({ 45 | error: { 46 | id: 'unable-to-save-subscription', 47 | message: 'The subscription was received but we were unable to save it to our database.' 48 | } 49 | }); 50 | }) 51 | }) 52 | 53 | app.post('/api/delete-subscription', async (req, res) => { 54 | subscriptions.findByIdAndDelete(req.body.id) 55 | .then(resp => { 56 | console.log(resp) 57 | res.setHeader('Content-Type', 'application/json'); 58 | res.send({ 59 | data: { 60 | success: true, 61 | } 62 | }); 63 | }) 64 | .catch(e => { 65 | res.status(500); 66 | res.setHeader('Content-Type', 'application/json'); 67 | res.send(JSON.stringify({ 68 | error: { 69 | id: 'unable-to-delete-subscription', 70 | message: 'Unable to delete subscription.' 71 | } 72 | })); 73 | }) 74 | }) 75 | 76 | app.post('/api/trigger-push-notification', async (req, res) => { 77 | const vapidKeys = { 78 | publicKey: 'BBDrHKLJfxVxcNKygxQzENzEb4mUPsuZrffu0EV6Hg_jyU5oR7LdG4bcrjHq7aWA5v8emlZpTMOIG68Wy5s8ofY', 79 | privateKey: 'bK22-rLBe0ortsJ44q2HdB28TegnYMLoYkpKlOV0HfE' 80 | }; 81 | 82 | webPush.setVapidDetails( 83 | 'mailto:web-push-book@gauntface.com', 84 | vapidKeys.publicKey, 85 | vapidKeys.privateKey 86 | ); 87 | 88 | subscriptions.findById(req.body.id) 89 | .then(subscription => { 90 | triggerPushMsg(subscription, JSON.stringify({ 91 | title: 'Title', 92 | message: 'Message' 93 | })); 94 | 95 | res.setHeader('Content-Type', 'application/json'); 96 | res.send(JSON.stringify({ data: { success: true } })); 97 | }) 98 | .catch(err => { 99 | res.status(500); 100 | res.setHeader('Content-Type', 'application/json'); 101 | res.send(JSON.stringify({ 102 | error: { 103 | id: 'unable-to-trigger-notification', 104 | message: 'Unable to fetch subscriptions.' 105 | } 106 | })); 107 | }) 108 | }) 109 | 110 | app.listen(process.env.PORT || 3001, () => console.log(`App running...`)) 111 | -------------------------------------------------------------------------------- /server/models/subscription.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const subscriptionSchema = new mongoose.Schema({ 4 | subscription: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | } 9 | }) 10 | 11 | const subscriptionModel = mongoose.model('subscriptions', subscriptionSchema) 12 | 13 | module.exports = subscriptionModel; -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-workbox-server", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "agent-base": { 17 | "version": "6.0.0", 18 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", 19 | "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", 20 | "requires": { 21 | "debug": "4" 22 | }, 23 | "dependencies": { 24 | "debug": { 25 | "version": "4.1.1", 26 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 27 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 28 | "requires": { 29 | "ms": "^2.1.1" 30 | } 31 | }, 32 | "ms": { 33 | "version": "2.1.2", 34 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 35 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 36 | } 37 | } 38 | }, 39 | "array-flatten": { 40 | "version": "1.1.1", 41 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 42 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 43 | }, 44 | "asn1.js": { 45 | "version": "5.4.1", 46 | "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", 47 | "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", 48 | "requires": { 49 | "bn.js": "^4.0.0", 50 | "inherits": "^2.0.1", 51 | "minimalistic-assert": "^1.0.0", 52 | "safer-buffer": "^2.1.0" 53 | } 54 | }, 55 | "bl": { 56 | "version": "2.2.0", 57 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", 58 | "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", 59 | "requires": { 60 | "readable-stream": "^2.3.5", 61 | "safe-buffer": "^5.1.1" 62 | } 63 | }, 64 | "bluebird": { 65 | "version": "3.5.1", 66 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 67 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 68 | }, 69 | "bn.js": { 70 | "version": "4.11.9", 71 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 72 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" 73 | }, 74 | "body-parser": { 75 | "version": "1.19.0", 76 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 77 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 78 | "requires": { 79 | "bytes": "3.1.0", 80 | "content-type": "~1.0.4", 81 | "debug": "2.6.9", 82 | "depd": "~1.1.2", 83 | "http-errors": "1.7.2", 84 | "iconv-lite": "0.4.24", 85 | "on-finished": "~2.3.0", 86 | "qs": "6.7.0", 87 | "raw-body": "2.4.0", 88 | "type-is": "~1.6.17" 89 | } 90 | }, 91 | "bson": { 92 | "version": "1.1.4", 93 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", 94 | "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==" 95 | }, 96 | "buffer-equal-constant-time": { 97 | "version": "1.0.1", 98 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 99 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 100 | }, 101 | "bytes": { 102 | "version": "3.1.0", 103 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 104 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 105 | }, 106 | "content-disposition": { 107 | "version": "0.5.3", 108 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 109 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 110 | "requires": { 111 | "safe-buffer": "5.1.2" 112 | } 113 | }, 114 | "content-type": { 115 | "version": "1.0.4", 116 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 117 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 118 | }, 119 | "cookie": { 120 | "version": "0.4.0", 121 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 122 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 123 | }, 124 | "cookie-signature": { 125 | "version": "1.0.6", 126 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 127 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 128 | }, 129 | "core-util-is": { 130 | "version": "1.0.2", 131 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 132 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 133 | }, 134 | "cors": { 135 | "version": "2.8.5", 136 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 137 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 138 | "requires": { 139 | "object-assign": "^4", 140 | "vary": "^1" 141 | } 142 | }, 143 | "debug": { 144 | "version": "2.6.9", 145 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 146 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 147 | "requires": { 148 | "ms": "2.0.0" 149 | } 150 | }, 151 | "denque": { 152 | "version": "1.4.1", 153 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 154 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 155 | }, 156 | "depd": { 157 | "version": "1.1.2", 158 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 159 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 160 | }, 161 | "destroy": { 162 | "version": "1.0.4", 163 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 164 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 165 | }, 166 | "ecdsa-sig-formatter": { 167 | "version": "1.0.11", 168 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 169 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 170 | "requires": { 171 | "safe-buffer": "^5.0.1" 172 | } 173 | }, 174 | "ee-first": { 175 | "version": "1.1.1", 176 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 177 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 178 | }, 179 | "encodeurl": { 180 | "version": "1.0.2", 181 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 182 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 183 | }, 184 | "escape-html": { 185 | "version": "1.0.3", 186 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 187 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 188 | }, 189 | "etag": { 190 | "version": "1.8.1", 191 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 192 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 193 | }, 194 | "express": { 195 | "version": "4.17.1", 196 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 197 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 198 | "requires": { 199 | "accepts": "~1.3.7", 200 | "array-flatten": "1.1.1", 201 | "body-parser": "1.19.0", 202 | "content-disposition": "0.5.3", 203 | "content-type": "~1.0.4", 204 | "cookie": "0.4.0", 205 | "cookie-signature": "1.0.6", 206 | "debug": "2.6.9", 207 | "depd": "~1.1.2", 208 | "encodeurl": "~1.0.2", 209 | "escape-html": "~1.0.3", 210 | "etag": "~1.8.1", 211 | "finalhandler": "~1.1.2", 212 | "fresh": "0.5.2", 213 | "merge-descriptors": "1.0.1", 214 | "methods": "~1.1.2", 215 | "on-finished": "~2.3.0", 216 | "parseurl": "~1.3.3", 217 | "path-to-regexp": "0.1.7", 218 | "proxy-addr": "~2.0.5", 219 | "qs": "6.7.0", 220 | "range-parser": "~1.2.1", 221 | "safe-buffer": "5.1.2", 222 | "send": "0.17.1", 223 | "serve-static": "1.14.1", 224 | "setprototypeof": "1.1.1", 225 | "statuses": "~1.5.0", 226 | "type-is": "~1.6.18", 227 | "utils-merge": "1.0.1", 228 | "vary": "~1.1.2" 229 | } 230 | }, 231 | "finalhandler": { 232 | "version": "1.1.2", 233 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 234 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 235 | "requires": { 236 | "debug": "2.6.9", 237 | "encodeurl": "~1.0.2", 238 | "escape-html": "~1.0.3", 239 | "on-finished": "~2.3.0", 240 | "parseurl": "~1.3.3", 241 | "statuses": "~1.5.0", 242 | "unpipe": "~1.0.0" 243 | } 244 | }, 245 | "forwarded": { 246 | "version": "0.1.2", 247 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 248 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 249 | }, 250 | "fresh": { 251 | "version": "0.5.2", 252 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 253 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 254 | }, 255 | "http-errors": { 256 | "version": "1.7.2", 257 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 258 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 259 | "requires": { 260 | "depd": "~1.1.2", 261 | "inherits": "2.0.3", 262 | "setprototypeof": "1.1.1", 263 | "statuses": ">= 1.5.0 < 2", 264 | "toidentifier": "1.0.0" 265 | } 266 | }, 267 | "http_ece": { 268 | "version": "1.1.0", 269 | "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.1.0.tgz", 270 | "integrity": "sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==", 271 | "requires": { 272 | "urlsafe-base64": "~1.0.0" 273 | } 274 | }, 275 | "https-proxy-agent": { 276 | "version": "5.0.0", 277 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 278 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 279 | "requires": { 280 | "agent-base": "6", 281 | "debug": "4" 282 | }, 283 | "dependencies": { 284 | "debug": { 285 | "version": "4.1.1", 286 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 287 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 288 | "requires": { 289 | "ms": "^2.1.1" 290 | } 291 | }, 292 | "ms": { 293 | "version": "2.1.2", 294 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 295 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 296 | } 297 | } 298 | }, 299 | "iconv-lite": { 300 | "version": "0.4.24", 301 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 302 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 303 | "requires": { 304 | "safer-buffer": ">= 2.1.2 < 3" 305 | } 306 | }, 307 | "inherits": { 308 | "version": "2.0.3", 309 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 310 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 311 | }, 312 | "ipaddr.js": { 313 | "version": "1.9.1", 314 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 315 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 316 | }, 317 | "isarray": { 318 | "version": "1.0.0", 319 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 320 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 321 | }, 322 | "jwa": { 323 | "version": "2.0.0", 324 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 325 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 326 | "requires": { 327 | "buffer-equal-constant-time": "1.0.1", 328 | "ecdsa-sig-formatter": "1.0.11", 329 | "safe-buffer": "^5.0.1" 330 | } 331 | }, 332 | "jws": { 333 | "version": "4.0.0", 334 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 335 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 336 | "requires": { 337 | "jwa": "^2.0.0", 338 | "safe-buffer": "^5.0.1" 339 | } 340 | }, 341 | "kareem": { 342 | "version": "2.3.1", 343 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", 344 | "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" 345 | }, 346 | "media-typer": { 347 | "version": "0.3.0", 348 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 349 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 350 | }, 351 | "memory-pager": { 352 | "version": "1.5.0", 353 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 354 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 355 | "optional": true 356 | }, 357 | "merge-descriptors": { 358 | "version": "1.0.1", 359 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 360 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 361 | }, 362 | "methods": { 363 | "version": "1.1.2", 364 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 365 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 366 | }, 367 | "mime": { 368 | "version": "1.6.0", 369 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 370 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 371 | }, 372 | "mime-db": { 373 | "version": "1.44.0", 374 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 375 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 376 | }, 377 | "mime-types": { 378 | "version": "2.1.27", 379 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 380 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 381 | "requires": { 382 | "mime-db": "1.44.0" 383 | } 384 | }, 385 | "minimalistic-assert": { 386 | "version": "1.0.1", 387 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", 388 | "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" 389 | }, 390 | "minimist": { 391 | "version": "1.2.5", 392 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 393 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 394 | }, 395 | "mongodb": { 396 | "version": "3.5.9", 397 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.9.tgz", 398 | "integrity": "sha512-vXHBY1CsGYcEPoVWhwgxIBeWqP3dSu9RuRDsoLRPTITrcrgm1f0Ubu1xqF9ozMwv53agmEiZm0YGo+7WL3Nbug==", 399 | "requires": { 400 | "bl": "^2.2.0", 401 | "bson": "^1.1.4", 402 | "denque": "^1.4.1", 403 | "require_optional": "^1.0.1", 404 | "safe-buffer": "^5.1.2", 405 | "saslprep": "^1.0.0" 406 | } 407 | }, 408 | "mongoose": { 409 | "version": "5.9.20", 410 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.20.tgz", 411 | "integrity": "sha512-vRP6Csu2obzSl3ed7kTQMrolBNgweiRJ/eBU1PSe/rJfjqWS1oqDE2D1ZPGxkVOsKXs7Gyd84GAXerj8IB2UWg==", 412 | "requires": { 413 | "bson": "^1.1.4", 414 | "kareem": "2.3.1", 415 | "mongodb": "3.5.9", 416 | "mongoose-legacy-pluralize": "1.0.2", 417 | "mpath": "0.7.0", 418 | "mquery": "3.2.2", 419 | "ms": "2.1.2", 420 | "regexp-clone": "1.0.0", 421 | "safe-buffer": "5.1.2", 422 | "sift": "7.0.1", 423 | "sliced": "1.0.1" 424 | }, 425 | "dependencies": { 426 | "ms": { 427 | "version": "2.1.2", 428 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 429 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 430 | } 431 | } 432 | }, 433 | "mongoose-legacy-pluralize": { 434 | "version": "1.0.2", 435 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 436 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 437 | }, 438 | "mpath": { 439 | "version": "0.7.0", 440 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", 441 | "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" 442 | }, 443 | "mquery": { 444 | "version": "3.2.2", 445 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", 446 | "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", 447 | "requires": { 448 | "bluebird": "3.5.1", 449 | "debug": "3.1.0", 450 | "regexp-clone": "^1.0.0", 451 | "safe-buffer": "5.1.2", 452 | "sliced": "1.0.1" 453 | }, 454 | "dependencies": { 455 | "debug": { 456 | "version": "3.1.0", 457 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 458 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 459 | "requires": { 460 | "ms": "2.0.0" 461 | } 462 | } 463 | } 464 | }, 465 | "ms": { 466 | "version": "2.0.0", 467 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 468 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 469 | }, 470 | "negotiator": { 471 | "version": "0.6.2", 472 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 473 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 474 | }, 475 | "object-assign": { 476 | "version": "4.1.1", 477 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 478 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 479 | }, 480 | "on-finished": { 481 | "version": "2.3.0", 482 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 483 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 484 | "requires": { 485 | "ee-first": "1.1.1" 486 | } 487 | }, 488 | "parseurl": { 489 | "version": "1.3.3", 490 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 491 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 492 | }, 493 | "path-to-regexp": { 494 | "version": "0.1.7", 495 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 496 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 497 | }, 498 | "process-nextick-args": { 499 | "version": "2.0.1", 500 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 501 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 502 | }, 503 | "proxy-addr": { 504 | "version": "2.0.6", 505 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 506 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 507 | "requires": { 508 | "forwarded": "~0.1.2", 509 | "ipaddr.js": "1.9.1" 510 | } 511 | }, 512 | "qs": { 513 | "version": "6.7.0", 514 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 515 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 516 | }, 517 | "range-parser": { 518 | "version": "1.2.1", 519 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 520 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 521 | }, 522 | "raw-body": { 523 | "version": "2.4.0", 524 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 525 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 526 | "requires": { 527 | "bytes": "3.1.0", 528 | "http-errors": "1.7.2", 529 | "iconv-lite": "0.4.24", 530 | "unpipe": "1.0.0" 531 | } 532 | }, 533 | "readable-stream": { 534 | "version": "2.3.7", 535 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 536 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 537 | "requires": { 538 | "core-util-is": "~1.0.0", 539 | "inherits": "~2.0.3", 540 | "isarray": "~1.0.0", 541 | "process-nextick-args": "~2.0.0", 542 | "safe-buffer": "~5.1.1", 543 | "string_decoder": "~1.1.1", 544 | "util-deprecate": "~1.0.1" 545 | } 546 | }, 547 | "regexp-clone": { 548 | "version": "1.0.0", 549 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 550 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 551 | }, 552 | "require_optional": { 553 | "version": "1.0.1", 554 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 555 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 556 | "requires": { 557 | "resolve-from": "^2.0.0", 558 | "semver": "^5.1.0" 559 | } 560 | }, 561 | "resolve-from": { 562 | "version": "2.0.0", 563 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 564 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 565 | }, 566 | "safe-buffer": { 567 | "version": "5.1.2", 568 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 569 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 570 | }, 571 | "safer-buffer": { 572 | "version": "2.1.2", 573 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 574 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 575 | }, 576 | "saslprep": { 577 | "version": "1.0.3", 578 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 579 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 580 | "optional": true, 581 | "requires": { 582 | "sparse-bitfield": "^3.0.3" 583 | } 584 | }, 585 | "semver": { 586 | "version": "5.7.1", 587 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 588 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 589 | }, 590 | "send": { 591 | "version": "0.17.1", 592 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 593 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 594 | "requires": { 595 | "debug": "2.6.9", 596 | "depd": "~1.1.2", 597 | "destroy": "~1.0.4", 598 | "encodeurl": "~1.0.2", 599 | "escape-html": "~1.0.3", 600 | "etag": "~1.8.1", 601 | "fresh": "0.5.2", 602 | "http-errors": "~1.7.2", 603 | "mime": "1.6.0", 604 | "ms": "2.1.1", 605 | "on-finished": "~2.3.0", 606 | "range-parser": "~1.2.1", 607 | "statuses": "~1.5.0" 608 | }, 609 | "dependencies": { 610 | "ms": { 611 | "version": "2.1.1", 612 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 613 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 614 | } 615 | } 616 | }, 617 | "serve-static": { 618 | "version": "1.14.1", 619 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 620 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 621 | "requires": { 622 | "encodeurl": "~1.0.2", 623 | "escape-html": "~1.0.3", 624 | "parseurl": "~1.3.3", 625 | "send": "0.17.1" 626 | } 627 | }, 628 | "setprototypeof": { 629 | "version": "1.1.1", 630 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 631 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 632 | }, 633 | "sift": { 634 | "version": "7.0.1", 635 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 636 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 637 | }, 638 | "sliced": { 639 | "version": "1.0.1", 640 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 641 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 642 | }, 643 | "sparse-bitfield": { 644 | "version": "3.0.3", 645 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 646 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 647 | "optional": true, 648 | "requires": { 649 | "memory-pager": "^1.0.2" 650 | } 651 | }, 652 | "statuses": { 653 | "version": "1.5.0", 654 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 655 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 656 | }, 657 | "string_decoder": { 658 | "version": "1.1.1", 659 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 660 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 661 | "requires": { 662 | "safe-buffer": "~5.1.0" 663 | } 664 | }, 665 | "toidentifier": { 666 | "version": "1.0.0", 667 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 668 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 669 | }, 670 | "type-is": { 671 | "version": "1.6.18", 672 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 673 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 674 | "requires": { 675 | "media-typer": "0.3.0", 676 | "mime-types": "~2.1.24" 677 | } 678 | }, 679 | "unpipe": { 680 | "version": "1.0.0", 681 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 682 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 683 | }, 684 | "urlsafe-base64": { 685 | "version": "1.0.0", 686 | "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", 687 | "integrity": "sha1-I/iQaabGL0bPOh07ABac77kL4MY=" 688 | }, 689 | "util-deprecate": { 690 | "version": "1.0.2", 691 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 692 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 693 | }, 694 | "utils-merge": { 695 | "version": "1.0.1", 696 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 697 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 698 | }, 699 | "vary": { 700 | "version": "1.1.2", 701 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 702 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 703 | }, 704 | "web-push": { 705 | "version": "3.4.4", 706 | "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.4.4.tgz", 707 | "integrity": "sha512-tB0F+ccobsfw5jTWBinWJKyd/YdCdRbKj+CFSnsJeEgFYysOULvWFYyeCxn9KuQvG/3UF1t3cTAcJzBec5LCWA==", 708 | "requires": { 709 | "asn1.js": "^5.3.0", 710 | "http_ece": "1.1.0", 711 | "https-proxy-agent": "^5.0.0", 712 | "jws": "^4.0.0", 713 | "minimist": "^1.2.5", 714 | "urlsafe-base64": "^1.0.0" 715 | } 716 | } 717 | } 718 | } 719 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awesome-web-apis-server", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "body-parser": "^1.19.0", 7 | "cors": "^2.8.5", 8 | "express": "^4.17.1", 9 | "mongoose": "^5.9.20", 10 | "web-push": "^3.4.4" 11 | }, 12 | "scripts": { 13 | "start": "node index.js" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom' 3 | import RouteLoader from './components/RouteLoader'; 4 | import logo512 from './images/logo512.png' 5 | import logo192 from './images/logo192.png' 6 | 7 | const Home = RouteLoader(React.lazy(() => import('./pages/others/Home'))) 8 | const About = RouteLoader(React.lazy(() => import('./pages/others/About'))) 9 | const Profile = RouteLoader(React.lazy(() => import('./pages/others/Profile'))) 10 | 11 | const Install = RouteLoader(React.lazy(() => import('./pages/apis/Install'))) 12 | const Battery = RouteLoader(React.lazy(() => import('./pages/apis/Battery'))) 13 | const Share = RouteLoader(React.lazy(() => import('./pages/apis/Share'))) 14 | const ShareTarget = RouteLoader(React.lazy(() => import('./pages/apis/ShareTarget'))) 15 | const Virate = RouteLoader(React.lazy(() => import('./pages/apis/Virate'))) 16 | const MediaSession = RouteLoader(React.lazy(() => import('./pages/apis/MediaSession'))) 17 | const FullScreen = RouteLoader(React.lazy(() => import('./pages/apis/FullScreen'))) 18 | const ScreenNoSleep = RouteLoader(React.lazy(() => import('./pages/apis/NoSleep'))) 19 | const WakeLock = RouteLoader(React.lazy(() => import('./pages/apis/WakeLock'))) 20 | const PushNotifications = RouteLoader(React.lazy(() => import('./pages/apis/PushNotifications'))) 21 | const TextFragments = RouteLoader(React.lazy(() => import('./pages/apis/TextFragments'))) 22 | const IdleDetector = RouteLoader(React.lazy(() => import('./pages/apis/IdleDetector'))) 23 | const UserMedia = RouteLoader(React.lazy(() => import('./pages/apis/UserMedia'))) 24 | const ScreenMedia = RouteLoader(React.lazy(() => import('./pages/apis/ScreenMedia'))) 25 | const Contacts = RouteLoader(React.lazy(() => import('./pages/apis/Contacts'))) 26 | const Clipboard = RouteLoader(React.lazy(() => import('./pages/apis/Clipboard'))) 27 | const PaymentRequest = RouteLoader(React.lazy(() => import('./pages/apis/PaymentRequest'))) 28 | const Call = RouteLoader(React.lazy(() => import('./pages/apis/Call'))) 29 | const PageVisibility = RouteLoader(React.lazy(() => import('./pages/apis/PageVisibility'))) 30 | const OnlineState = RouteLoader(React.lazy(() => import('./pages/apis/OnlineState'))) 31 | const NetworkInformation = RouteLoader(React.lazy(() => import('./pages/apis/NetworkInformation'))) 32 | const GeoLocation = RouteLoader(React.lazy(() => import('./pages/apis/GeoLocation'))) 33 | const IntersectionObserver = RouteLoader(React.lazy(() => import('./pages/apis/IntersectionObserver'))) 34 | const DeviceMemory = RouteLoader(React.lazy(() => import('./pages/apis/DeviceMemory'))) 35 | const StorageQuota = RouteLoader(React.lazy(() => import('./pages/apis/StorageQuota'))) 36 | const AmbientLightSensor = RouteLoader(React.lazy(() => import('./pages/apis/AmbientLightSensor'))) 37 | const FileRead = RouteLoader(React.lazy(() => import('./pages/apis/FileRead'))) 38 | const FileWrite = RouteLoader(React.lazy(() => import('./pages/apis/FileWrite'))) 39 | 40 | function App() { 41 | return ( 42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | 82 | {/* ddd */} 83 | {/* ddd */} 84 |
85 |
86 | ); 87 | } 88 | 89 | export default App; 90 | -------------------------------------------------------------------------------- /src/components/Back.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useHistory } from 'react-router' 3 | 4 | const Back = () => { 5 | const { goBack } = useHistory() 6 | 7 | const back = e => { 8 | e.preventDefault(); 9 | goBack() 10 | } 11 | 12 | return ( 13 |
14 | {'<< Back'} 15 |
16 | ) 17 | } 18 | 19 | export default Back; -------------------------------------------------------------------------------- /src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | const Header = () => { 5 | return ( 6 | <> 7 |
8 |

Pages

{' '} 9 | Home{' '} 10 | About{' '} 11 | Profile 12 |
13 | 14 |
15 |

Interesting Web APIs

16 | Install 17 | Share 18 | Share Target 19 | Vibrate 20 | MediaSession 21 | FullScreen 22 | Wake lock with NoSleep.js 23 | WakeLock 24 | Push Notifications 25 | Text Fragments 26 | Idle Detector 27 | User Media 28 | Screen Media 29 | Contacts 30 | Clipboard 31 | PaymentRequest 32 | Call 33 | Battery 34 | Page Visibility 35 | Online State 36 | Network Information 37 | GeoLocation 38 | Intersection Observer 39 | Device Memory 40 | Storage Quota 41 | Ambient Light Sensor 42 | File Read 43 | File Write 44 |
45 | 46 | ) 47 | } 48 | 49 | export default Header; 50 | -------------------------------------------------------------------------------- /src/components/LazyImage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react' 2 | import NativeIntersectionObserver from '../lib/NativeIntersectionObserver' 3 | 4 | const LazyImage = props => { 5 | 6 | const ref = useRef(null) 7 | const [src, setSrc] = useState('/placeholder.png') 8 | 9 | useEffect(() => { 10 | // We dont need separate Observer for each image 11 | if (!window.imgObserver) { 12 | window.imgObserver = new NativeIntersectionObserver({ 13 | options: { threshold: 0 }, 14 | observeOnce: true, 15 | }); 16 | } 17 | window.imgObserver.observe(ref.current, () => { 18 | setSrc(props.src) 19 | }); 20 | }, [props.src]); 21 | 22 | return ( 23 | 24 | ) 25 | } 26 | 27 | export default LazyImage; 28 | -------------------------------------------------------------------------------- /src/components/ReactIntersectionObserver.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import NativeIntersectionObserver from '../lib/NativeIntersectionObserver'; 3 | 4 | /* 5 | A react wrapper around lib/Observer to use on react components. 6 | https://codesandbox.io/s/0xq11z660v 7 | */ 8 | const ReactIntersectionObserver = ({ children, options, onIntersect }) => { 9 | const ref = useRef(); 10 | 11 | useEffect(() => { 12 | const observer = new NativeIntersectionObserver({ 13 | options, 14 | }); 15 | 16 | observer.observe(ref.current, onIntersect); 17 | 18 | return () => { 19 | observer.unobserve(ref.current); 20 | }; 21 | }, [onIntersect, options]); 22 | 23 | return
{children}
; 24 | }; 25 | 26 | export default ReactIntersectionObserver; 27 | -------------------------------------------------------------------------------- /src/components/RouteLoader.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | 3 | const RouteLoader = Component => props => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default RouteLoader; -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export const urlB64ToUint8Array = base64String => { 2 | const padding = '='.repeat((4 - base64String.length % 4) % 4); 3 | const base64 = (base64String + padding) 4 | .replace(/\-/g, '+') 5 | .replace(/_/g, '/'); 6 | 7 | const rawData = window.atob(base64); 8 | const outputArray = new Uint8Array(rawData.length); 9 | 10 | for (let i = 0; i < rawData.length; ++i) { 11 | outputArray[i] = rawData.charCodeAt(i); 12 | } 13 | return outputArray; 14 | } 15 | 16 | export const formatBytes = (bytes, decimals = 2) => { 17 | if (bytes === 0) return '0 Bytes'; 18 | 19 | const k = 1024; 20 | const dm = decimals < 0 ? 0 : decimals; 21 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 22 | 23 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 24 | 25 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 26 | } -------------------------------------------------------------------------------- /src/images/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/src/images/logo192.png -------------------------------------------------------------------------------- /src/images/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yTakkar/modern-web-apis/114b816fcd9baaf80677366bb61b9d7e80a03bd6/src/images/logo512.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | import './logo.svg'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | if (false) { 18 | serviceWorker.register(); 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/NativeIntersectionObserver.js: -------------------------------------------------------------------------------- 1 | /* 2 | Library to handle intersection observer functionalities. 3 | https://codesandbox.io/s/0xq11z660v 4 | */ 5 | export class NativeIntersectionObserver { 6 | options = { 7 | threshold: 0, 8 | }; 9 | _elementMap = new Map(); 10 | obsvr = {}; 11 | 12 | // when set to true, callback will be execute only once for a target 13 | observeOnce = false; 14 | 15 | // Initialize options 16 | constructor({ options, observeOnce = false }) { 17 | this.options = options; 18 | this.observeOnce = observeOnce; 19 | this._elementMap = new Map(); 20 | this.obsvr = new IntersectionObserver(this.callBack, this.options); 21 | } 22 | 23 | // Intersection observer callback method 24 | callBack = (entries) => { 25 | entries.forEach(entry => { 26 | if (!entry.isIntersecting) return; 27 | this._elementMap.get(entry.target)(entry); 28 | this.observeOnce && this.unobserve(entry.target); 29 | }); 30 | }; 31 | 32 | // Consumer calls this public method to observe an element 33 | observe = (target, onIntersect) => { 34 | if (!target) return; 35 | this._elementMap.set(target, onIntersect); 36 | this.obsvr.observe(target); 37 | }; 38 | 39 | // Consumer calls this public method to unobserve an element 40 | unobserve = (target) => { 41 | if (!target) return; 42 | this.obsvr.unobserve(target); 43 | this._elementMap.delete(target); 44 | }; 45 | } 46 | 47 | export default NativeIntersectionObserver; 48 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pages/apis/AmbientLightSensor.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const _AmbientLightSensor = props => { 5 | const [error, setError] = useState(null) 6 | const [light, setLight] = useState(null) 7 | 8 | useEffect(() => { 9 | if ("AmbientLightSensor" in window) { 10 | const sensor = new window.AmbientLightSensor() 11 | 12 | sensor.onreading = () => { 13 | setLight(sensor.illuminance); 14 | } 15 | 16 | sensor.onerror = (event) => { 17 | setError(event.error.message) 18 | } 19 | 20 | sensor.start(); 21 | 22 | } else { 23 | setError('API is not supported in your browser') 24 | } 25 | }, []) 26 | 27 | return ( 28 |
29 | 30 | 31 |
32 | 33 |
34 | Current light level: {light || ''} 35 |
36 | 37 |
38 | 39 |
{error}
40 |
41 | ) 42 | } 43 | 44 | export default _AmbientLightSensor; 45 | -------------------------------------------------------------------------------- /src/pages/apis/Battery.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const Battery = props => { 5 | const [level, setLevel] = useState(0) 6 | const [isCharging, setIsCharging] = useState(false) 7 | const [errorMessage, setErrorMessage] = useState('') 8 | 9 | useEffect(() => { 10 | if (navigator.getBattery) { 11 | navigator.getBattery() 12 | .then(battery => { 13 | 14 | const updateState = () => { 15 | setLevel(battery.level) 16 | setIsCharging(battery.charging) 17 | } 18 | 19 | updateState(); 20 | 21 | battery.addEventListener('levelchange', e => { 22 | console.log('levelchange') 23 | updateState() 24 | }) 25 | 26 | battery.addEventListener('chargingchange', e => { 27 | console.log('chargingchange') 28 | updateState() 29 | }) 30 | }) 31 | .catch(e => setErrorMessage(e.message)) 32 | } 33 | }, []) 34 | 35 | return ( 36 |
37 | 38 |

Battery API

39 | 40 |
41 |
Battery level: {level*100}%
42 |
Is charging: {`${isCharging}`}
43 |
44 | 45 |
46 |
{errorMessage}
47 |
48 | ) 49 | } 50 | 51 | export default Battery; -------------------------------------------------------------------------------- /src/pages/apis/Call.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const Call = props => { 5 | return ( 6 |
7 | 8 | 9 |

Click to Call from both desktop and mobile

10 | 11 | Call me? 12 | 13 |
14 | ) 15 | } 16 | 17 | export default Call; -------------------------------------------------------------------------------- /src/pages/apis/Clipboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Back from '../../components/Back'; 3 | 4 | const Clipboard = props => { 5 | const copyValue = 'quick brown fox jumps over the lazy dog'; 6 | 7 | const [pasteValue, setPasteValue] = useState('') 8 | const [errorMessage, setErrorMessage] = useState('') 9 | 10 | const copy = () => { 11 | navigator.clipboard.writeText(copyValue) 12 | } 13 | 14 | const paste = () => { 15 | navigator.clipboard.readText() 16 | .then(setPasteValue) 17 | .catch(e => setErrorMessage(e.message)) 18 | } 19 | 20 | const copyImage = async () => { 21 | try { 22 | const response = await fetch( 23 | 'https://a2.espncdn.com/combiner/i?img=%2Fi%2Fcricket%2Fcricinfo%2F1225387_1296x729.jpg&w=360&h=360&scale=crop&cquality=80&location=origin' 24 | ) 25 | const blob = await response.blob() 26 | const data = new ClipboardItem({ // eslint-disable-line 27 | [blob.type]: blob 28 | }) 29 | 30 | await navigator.clipboard.writeText([ data ]) 31 | 32 | } catch (e) { 33 | setErrorMessage(e.message) 34 | } 35 | } 36 | 37 | return ( 38 |
39 | 40 | 41 |

Clipboard API

42 | 43 |
Copy value: {copyValue}
44 |
Paste value: {pasteValue}
45 | 46 |
47 |   48 | 49 | 50 |
51 |
52 | 53 |
54 | Taj Mahal 1 55 |
56 | 57 | 58 |
59 |
60 |
{errorMessage}
61 |
62 | ) 63 | } 64 | 65 | export default Clipboard; -------------------------------------------------------------------------------- /src/pages/apis/Contacts.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const Contacts = props => { 5 | const [contacts, setContacts] = useState([]) 6 | const [errorMessage, setErrorMessage] = useState('') 7 | 8 | const fetchContacts = () => { 9 | var api = (navigator.contacts || navigator.mozContacts); 10 | 11 | const chromeContactProps = ['name', 'tel', 'email'] 12 | 13 | if (api && !!api.select) { // new Chrome API 14 | api.select(chromeContactProps, { multiple: true }) 15 | .then(setContacts) 16 | .catch(e => setErrorMessage(e.message)); 17 | 18 | } else if (api && !!api.find) { // old Firefox OS API 19 | var criteria = { 20 | sortBy: 'familyName', 21 | sortOrder: 'ascending' 22 | }; 23 | 24 | api.find(criteria) 25 | .then(setContacts) 26 | .catch(err => setErrorMessage(err.message)); 27 | 28 | } else { 29 | setErrorMessage('Contacts API not supported.'); 30 | } 31 | } 32 | 33 | return ( 34 |
35 | 36 | 37 |

Contacts API

38 | 39 |
40 |
41 | 42 | {contacts.map((contact, index) => { 43 | return ( 44 | <> 45 |
46 |
47 | Name: {contact.name[0]} 48 |
49 |
50 | Email: {contact.email[0]} 51 |
52 |
53 | Phone numbers: 54 |
    55 | {contact.tel.map((t, i) =>
  • {t}
  • )} 56 |
57 |
58 |
59 |
60 | 61 | ) 62 | })} 63 | 64 |
{errorMessage}
65 |
66 | ) 67 | } 68 | 69 | export default Contacts; -------------------------------------------------------------------------------- /src/pages/apis/DeviceMemory.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const DeviceMemory = props => { 5 | const [error, setError] = useState(null) 6 | const [deviceMemory, setDeviceMemory] = useState(null) 7 | 8 | useEffect(() => { 9 | if (!navigator.deviceMemory) { 10 | setError('API is not supported in your browser.') 11 | } else { 12 | setDeviceMemory(navigator.deviceMemory) 13 | } 14 | }, []) 15 | 16 | return ( 17 |
18 | 19 | 20 |
21 | 22 |
23 | {error ? error : `Your RAM size is ${deviceMemory}`} 24 |
25 |
26 | ) 27 | } 28 | 29 | export default DeviceMemory; -------------------------------------------------------------------------------- /src/pages/apis/FileRead.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useState, useEffect } from 'react' 3 | import Back from '../../components/Back' 4 | 5 | const FileRead = props => { 6 | const [error, setError] = useState(null) 7 | const [file, setFile] = useState(null) 8 | const [fileContent, setFileContent] = useState(null) 9 | 10 | useEffect(() => { 11 | if (!file) { 12 | setFileContent(null) 13 | } 14 | }, [file]) 15 | 16 | const onFileChange = e => { 17 | const { files } = e.target 18 | 19 | setFile(files[0]) 20 | 21 | if (!window.FileReader) { 22 | setError('API is not supported in your browser') 23 | } else { 24 | for (let file of files) { 25 | const reader = new FileReader() 26 | 27 | reader.addEventListener('load', ev => { 28 | let fileContent = { 29 | type: file.type, 30 | content: ev.target.result, 31 | } 32 | setFileContent(fileContent) 33 | }) 34 | 35 | if (file.type.indexOf('image') !== -1) { 36 | reader.readAsDataURL(file) 37 | } else { 38 | reader.readAsText(file) 39 | } 40 | } 41 | } 42 | } 43 | 44 | const renderContent = () => { 45 | if (!fileContent) { 46 | return null 47 | } 48 | 49 | if (fileContent.type.indexOf('image') !== -1) { 50 | return File preview 51 | } else { 52 | return 53 | } 54 | } 55 | 56 | return ( 57 |
58 | 59 |
60 | 61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 | {renderContent()} 69 |
70 | 71 |
{error}
72 |
73 | ) 74 | } 75 | 76 | export default FileRead 77 | -------------------------------------------------------------------------------- /src/pages/apis/FileWrite.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useState, useEffect, useRef } from 'react' 3 | import Back from '../../components/Back' 4 | import { fileOpen, fileSave } from 'browser-fs-access' 5 | 6 | const FileWrite = props => { 7 | const [error, setError] = useState(null) 8 | const [fileContent, setFileContent] = useState(null) 9 | const fileBlob = useRef(null) 10 | const fileHandler = useRef(null) 11 | 12 | useEffect(() => { 13 | if (!window.showOpenFilePicker) { 14 | setError('API is not supported in your browser') 15 | } 16 | }, []) 17 | 18 | const openFile = e => { 19 | fileOpen({ 20 | multiple: false, 21 | mimeTypes: ['text/*'] 22 | }) 23 | .then(async blob => { 24 | fileBlob.current = blob 25 | fileHandler.current = blob.handle; 26 | 27 | const file = await blob.handle.getFile(); 28 | const content = await file.text(); 29 | setFileContent(content) 30 | }) 31 | .catch(e => { 32 | setError(e.message) 33 | }) 34 | } 35 | 36 | const saveFile = () => { 37 | fileHandler.current.createWritable() 38 | .then(async writable => { 39 | await writable.write(fileContent); 40 | await writable.close(); 41 | }) 42 | .catch(e => { 43 | setError(e.message) 44 | }) 45 | } 46 | 47 | const saveAsFile = () => { 48 | fileSave(fileBlob.current, {}) 49 | .then(fileHandle => { 50 | fileHandler.current = fileHandle; 51 | }) 52 | .catch(e => { 53 | setError(e.message) 54 | }) 55 | } 56 | 57 | const renderFileEditor = () => { 58 | if (!fileContent) { 59 | return null 60 | } 61 | 62 | return ( 63 |
64 | 65 |
66 | 67 | 68 |
69 |
70 | ) 71 | } 72 | 73 | return ( 74 |
75 | 76 | 77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 | 85 | {renderFileEditor()} 86 | 87 |
88 | 89 |
{error}
90 |
91 | ) 92 | } 93 | 94 | export default FileWrite 95 | -------------------------------------------------------------------------------- /src/pages/apis/FullScreen.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from 'react' 2 | import Back from '../../components/Back'; 3 | 4 | const FullScreen = props => { 5 | const [isFullscreenView, toggleFullscreenView] = useState(false) 6 | const imgRef = useRef(null); 7 | 8 | useEffect(() => { 9 | const onChange = e => { 10 | toggleFullscreenView(!isFullscreenView) 11 | } 12 | 13 | document.addEventListener('fullscreenchange', onChange) 14 | 15 | return () => { 16 | document.removeEventListener('fullscreenchange', onChange) 17 | } 18 | }, [isFullscreenView]) 19 | 20 | const goFullscreen = () => { 21 | if (!isFullscreenView) { 22 | imgRef.current.requestFullscreen() 23 | } else { 24 | document.exitFullscreen(); 25 | } 26 | } 27 | 28 | return ( 29 |
30 | 31 | 32 |

FullScreen API

33 | TajMahal 36 |
37 | Click on the image to toggle the above image in fullscreen mode. 38 |
39 |
40 |
41 | ) 42 | } 43 | 44 | export default FullScreen; -------------------------------------------------------------------------------- /src/pages/apis/GeoLocation.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useState, useEffect, useRef } from 'react' 3 | import Back from '../../components/Back' 4 | 5 | const ERROR_CODE_MESSAGES = { 6 | 1: 'You have denied the permission', 7 | 2: 'Unable to fetch your location', 8 | 3: 'Took too long to fetch your location' 9 | } 10 | 11 | const GeoLocation = props => { 12 | 13 | const [error, setError] = useState(null) 14 | 15 | const [currentPosition, setCurrentPosition] = useState({ 16 | latitude: null, 17 | longitude: null, 18 | }) 19 | 20 | const onSuccess = (position) => { 21 | const { coords } = position; 22 | setCurrentPosition({ 23 | latitude: coords.latitude, 24 | longitude: coords.longitude, 25 | }) 26 | } 27 | 28 | const onError = (error) => { 29 | setError(ERROR_CODE_MESSAGES[error.code]) 30 | } 31 | 32 | const getCurrentPosition = () => { 33 | if (navigator.geolocation) { 34 | navigator.geolocation.getCurrentPosition(onSuccess, onError) 35 | } else { 36 | setError('API not supported in your browser!') 37 | } 38 | } 39 | 40 | return ( 41 |
42 | 43 | 44 |
45 | 46 | 47 | 48 |
49 |
50 | 51 | {currentPosition.latitude ?
52 |
Latitude: {currentPosition.latitude}
53 |
Longitude: {currentPosition.longitude}
54 |
: null} 55 | 56 |
{error}
57 |
58 | ) 59 | } 60 | 61 | export default GeoLocation; -------------------------------------------------------------------------------- /src/pages/apis/IdleDetector.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const IdleDetectorApi = props => { 5 | const [idleState, setIdleState] = useState({}) 6 | const [isDetectionOn, toggleDetection] = useState(false) 7 | const [errorMessage, setErrorMessage] = useState('') 8 | 9 | const controller = new AbortController(); 10 | 11 | const startIdleDetection = () => { 12 | if (window.IdleDetector) { 13 | // eslint-disable-next-line 14 | const idleDetector = new IdleDetector() 15 | idleDetector.addEventListener('change', () => { 16 | console.log(idleDetector.state) 17 | setIdleState(idleDetector.state); 18 | }) 19 | 20 | idleDetector.start({ 21 | threshold: 60 * 1000, // minimum 60 secs 22 | signal: controller.signal 23 | }).then(() => { 24 | console.log('IdleDetector is active.') 25 | toggleDetection(true) 26 | }) 27 | } else { 28 | setErrorMessage('IdleDetector is not supported in your browser') 29 | } 30 | } 31 | 32 | const stopIdleDetection = () => { 33 | controller.abort() 34 | toggleDetection(false) 35 | setIdleState({}) 36 | } 37 | 38 | return ( 39 |
40 | 41 | 42 |

Idle Detector

43 |
The API can help use detect whether user is idle or the screen is locked.
44 |
45 | 46 | {!isDetectionOn 47 | ? 48 | : 49 | } 50 | 51 |
52 |
User: {idleState.user}
53 |
Screen: {idleState.screen}
54 |
55 | 56 |
57 |
{errorMessage}
58 |
59 | ) 60 | } 61 | 62 | export default IdleDetectorApi; -------------------------------------------------------------------------------- /src/pages/apis/Install.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react' 2 | import Back from '../../components/Back'; 3 | 4 | const Install = props => { 5 | const [show, toggle] = useState(false); 6 | const prompt = useRef(undefined); 7 | 8 | useEffect(() => { 9 | window.addEventListener('beforeinstallprompt', e => { 10 | console.log('Received beforeinstallprompt event', e); 11 | e.preventDefault(); // hide in-built install UI 12 | prompt.current = e; // store prompt object 13 | toggle(true); // show custom UI 14 | }); 15 | 16 | window.addEventListener('appinstalled', e => { 17 | setTimeout(() => { 18 | toggle(false) 19 | }, 1500) 20 | }) 21 | }, []); 22 | 23 | const showPrompt = () => { 24 | prompt.current.prompt(); // show prompt 25 | prompt.current.userChoice.then(choiceResult => { 26 | if (choiceResult.outcome === 'accepted') { 27 | console.log('User accepted the install prompt'); 28 | toggle(false) 29 | } else { 30 | console.log('User dismissed the install prompt'); 31 | toggle(true) 32 | } 33 | }); 34 | }; 35 | 36 | return ( 37 |
38 | 39 | 40 |
41 | {show ? 42 | 43 | :
You either have already installed the app or installation is not supported in your browser
44 | } 45 |
46 | ) 47 | } 48 | 49 | export default Install; -------------------------------------------------------------------------------- /src/pages/apis/IntersectionObserver.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react' 2 | import Back from '../../components/Back'; 3 | import LazyImage from '../../components/LazyImage'; 4 | import ReactIntersectionObserver from '../../components/ReactIntersectionObserver' 5 | 6 | const IntersectionObserver = props => { 7 | const imagesList = [ 8 | '/taj-mahal-1.jpg', 9 | '/taj-mahal-2.jpg', 10 | '/taj-mahal-3.jpg', 11 | '/taj-mahal-4.jpg', 12 | '/taj-mahal-1.jpg', 13 | '/taj-mahal-2.jpg', 14 | '/taj-mahal-3.jpg', 15 | '/taj-mahal-4.jpg', 16 | '/taj-mahal-1.jpg', 17 | '/taj-mahal-2.jpg', 18 | '/taj-mahal-3.jpg', 19 | '/taj-mahal-4.jpg', 20 | '/taj-mahal-1.jpg', 21 | '/taj-mahal-2.jpg', 22 | '/taj-mahal-3.jpg', 23 | '/taj-mahal-4.jpg', 24 | ] 25 | 26 | const [error, setError] = useState(null) 27 | const infiniteScrollRef = useRef(null) 28 | 29 | useEffect(() => { 30 | if (!window.IntersectionObserver) { 31 | setError('API is not supported in your browser') 32 | } 33 | }, []) 34 | 35 | const [images, setImages] = useState(imagesList) 36 | 37 | const onInfiniteScrollIntersect = () => { 38 | setImages([ 39 | ...images, 40 | ...imagesList, 41 | ]) 42 | } 43 | 44 | return ( 45 |
46 | 47 |
48 | 49 |
Images below will be lazy-loaded. There's also support for infinite scroll.
50 |
51 | 52 | {images.map((image, index) => 53 |
54 | 55 |
56 | )} 57 | 58 |
59 | 60 | 66 |
{error}
69 |
70 | 71 |
72 | ) 73 | } 74 | 75 | export default IntersectionObserver; -------------------------------------------------------------------------------- /src/pages/apis/MediaSession.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react' 2 | import Back from '../../components/Back'; 3 | 4 | const getAwesomePlaylist = () => { 5 | const BASE_URL = 'https://storage.googleapis.com/media-session/'; 6 | 7 | return [{ 8 | src: BASE_URL + 'sintel/snow-fight.mp3', 9 | title: 'Snow Fight', 10 | artist: 'Jan Morgenstern', 11 | album: 'Sintel', 12 | artwork: [ 13 | { src: BASE_URL + 'sintel/artwork-96.png', sizes: '96x96', type: 'image/png' }, 14 | { src: BASE_URL + 'sintel/artwork-128.png', sizes: '128x128', type: 'image/png' }, 15 | { src: BASE_URL + 'sintel/artwork-192.png', sizes: '192x192', type: 'image/png' }, 16 | { src: BASE_URL + 'sintel/artwork-256.png', sizes: '256x256', type: 'image/png' }, 17 | { src: BASE_URL + 'sintel/artwork-384.png', sizes: '384x384', type: 'image/png' }, 18 | { src: BASE_URL + 'sintel/artwork-512.png', sizes: '512x512', type: 'image/png' }, 19 | ] 20 | }, { 21 | src: BASE_URL + 'big-buck-bunny/prelude.mp3', 22 | title: 'Prelude', 23 | artist: 'Jan Morgenstern', 24 | album: 'Big Buck Bunny', 25 | artwork: [ 26 | { src: BASE_URL + 'big-buck-bunny/artwork-96.png', sizes: '96x96', type: 'image/png' }, 27 | { src: BASE_URL + 'big-buck-bunny/artwork-128.png', sizes: '128x128', type: 'image/png' }, 28 | { src: BASE_URL + 'big-buck-bunny/artwork-192.png', sizes: '192x192', type: 'image/png' }, 29 | { src: BASE_URL + 'big-buck-bunny/artwork-256.png', sizes: '256x256', type: 'image/png' }, 30 | { src: BASE_URL + 'big-buck-bunny/artwork-384.png', sizes: '384x384', type: 'image/png' }, 31 | { src: BASE_URL + 'big-buck-bunny/artwork-512.png', sizes: '512x512', type: 'image/png' }, 32 | ] 33 | }, { 34 | src: BASE_URL + 'elephants-dream/the-wires.mp3', 35 | title: 'The Wires', 36 | artist: 'Jan Morgenstern', 37 | album: 'Elephants Dream', 38 | artwork: [ 39 | { src: BASE_URL + 'elephants-dream/artwork-96.png', sizes: '96x96', type: 'image/png' }, 40 | { src: BASE_URL + 'elephants-dream/artwork-128.png', sizes: '128x128', type: 'image/png' }, 41 | { src: BASE_URL + 'elephants-dream/artwork-192.png', sizes: '192x192', type: 'image/png' }, 42 | { src: BASE_URL + 'elephants-dream/artwork-256.png', sizes: '256x256', type: 'image/png' }, 43 | { src: BASE_URL + 'elephants-dream/artwork-384.png', sizes: '384x384', type: 'image/png' }, 44 | { src: BASE_URL + 'elephants-dream/artwork-512.png', sizes: '512x512', type: 'image/png' }, 45 | ] 46 | }, { 47 | src: BASE_URL + 'caminandes/original-score.mp3', 48 | title: 'Original Score', 49 | artist: 'Jan Morgenstern', 50 | album: 'Caminandes 2: Gran Dillama', 51 | artwork: [ 52 | { src: BASE_URL + 'caminandes/artwork-96.png', sizes: '96x96', type: 'image/png' }, 53 | { src: BASE_URL + 'caminandes/artwork-128.png', sizes: '128x128', type: 'image/png' }, 54 | { src: BASE_URL + 'caminandes/artwork-192.png', sizes: '192x192', type: 'image/png' }, 55 | { src: BASE_URL + 'caminandes/artwork-256.png', sizes: '256x256', type: 'image/png' }, 56 | { src: BASE_URL + 'caminandes/artwork-384.png', sizes: '384x384', type: 'image/png' }, 57 | { src: BASE_URL + 'caminandes/artwork-512.png', sizes: '512x512', type: 'image/png' }, 58 | ] 59 | }]; 60 | } 61 | 62 | const MediaSession = props => { 63 | const audioRef = useRef(null) 64 | const [audioIndex, setAudioIndex] = useState(0) 65 | const playlist = getAwesomePlaylist() 66 | const currentSong = playlist[audioIndex]; 67 | 68 | const updateMetadata = () => { 69 | console.log('Playing ' + currentSong.title + ' track...'); 70 | 71 | // eslint-disable-next-line 72 | navigator.mediaSession.metadata = new MediaMetadata({ 73 | title: currentSong.title, 74 | artist: currentSong.artist, 75 | album: currentSong.album, 76 | artwork: currentSong.artwork 77 | }); 78 | 79 | updatePositionState(); 80 | } 81 | 82 | const updatePositionState = () => { 83 | console.log('Updating position state...'); 84 | 85 | navigator.mediaSession.setPositionState({ 86 | duration: audioRef.current.duration, 87 | playbackRate: audioRef.current.playbackRate, 88 | position: audioRef.current.currentTime 89 | }); 90 | } 91 | 92 | const playAudio = () => { 93 | audioRef.current.play() 94 | .then(() => updateMetadata()) 95 | .catch(console.log) 96 | } 97 | 98 | useEffect(() => { 99 | navigator.mediaSession.setActionHandler('previoustrack', function() { 100 | console.log('> User clicked "Previous Track" icon.'); 101 | setAudioIndex((audioIndex - 1 + playlist.length) % playlist.length); 102 | playAudio(); 103 | }); 104 | 105 | navigator.mediaSession.setActionHandler('nexttrack', function() { 106 | console.log('> User clicked "Next Track" icon.'); 107 | setAudioIndex((audioIndex + 1) % playlist.length) 108 | playAudio(); 109 | }); 110 | 111 | audioRef.current.addEventListener('ended', function() { 112 | // Play automatically the next track when audio ends. 113 | setAudioIndex((audioIndex - 1 + playlist.length) % playlist.length); 114 | playAudio(); 115 | }); 116 | 117 | let defaultSkipTime = 10; /* Time to skip in seconds by default */ 118 | 119 | navigator.mediaSession.setActionHandler('seekbackward', function(event) { 120 | console.log('> User clicked "Seek Backward" icon.'); 121 | const skipTime = event.seekOffset || defaultSkipTime; 122 | audioRef.current.currentTime = Math.max(audioRef.current.currentTime - skipTime, 0); 123 | updatePositionState(); 124 | }); 125 | 126 | navigator.mediaSession.setActionHandler('seekforward', function(event) { 127 | console.log('> User clicked "Seek Forward" icon.'); 128 | const skipTime = event.seekOffset || defaultSkipTime; 129 | audioRef.current.currentTime = Math.min(audioRef.current.currentTime + skipTime, audioRef.current.duration); 130 | updatePositionState(); 131 | }); 132 | 133 | navigator.mediaSession.setActionHandler('play', async function() { 134 | console.log('> User clicked "Play" icon.'); 135 | await audioRef.current.play(); 136 | navigator.mediaSession.playbackState = "playing"; 137 | // Do something more than just playing audio... 138 | }); 139 | 140 | navigator.mediaSession.setActionHandler('pause', async function() { 141 | console.log('> User clicked "Pause" icon.'); 142 | audioRef.current.pause(); 143 | navigator.mediaSession.playbackState = "paused"; 144 | // Do something more than just pausing audio... 145 | }); 146 | }, [audioIndex]) // eslint-disable-line 147 | 148 | return ( 149 |
150 | 151 | 152 |

MediaSession API

153 | 154 | 157 |
158 | ) 159 | } 160 | 161 | export default MediaSession; -------------------------------------------------------------------------------- /src/pages/apis/NetworkInformation.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useState, useEffect, useRef } from 'react' 3 | import Back from '../../components/Back' 4 | 5 | const NetworkInformation = props => { 6 | const getConnection = () => { 7 | return navigator.connection || navigator.mozConnection || navigator.webkitConnection || navigator.msConnection; 8 | } 9 | 10 | const [error, setError] = useState(null) 11 | const [networkInformation, setNetworkInformation] = useState({ 12 | networkType: null, 13 | effectiveType: null, 14 | }) 15 | const connection = useRef(getConnection()) 16 | 17 | const updateNetworkInformation = () => { 18 | setNetworkInformation({ 19 | networkType: connection.current.type, 20 | effectiveType: connection.current.effectiveType, 21 | }) 22 | } 23 | 24 | useEffect(() => { 25 | if (connection.current) { 26 | updateNetworkInformation() 27 | } else { 28 | setError('API not supported in your browser.') 29 | } 30 | }, []) 31 | 32 | return ( 33 |
34 | 35 | 36 |
37 | 38 | {error ?
{error}
:
39 |
Network Type: {networkInformation.networkType || ''}
40 |
Effective Type: {networkInformation.effectiveType || ''}
41 |
} 42 | 43 |
44 | ) 45 | } 46 | 47 | export default NetworkInformation; -------------------------------------------------------------------------------- /src/pages/apis/NoSleep.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import NoSleep from 'nosleep.js' 3 | import Back from '../../components/Back'; 4 | 5 | const noSleep = new NoSleep(); 6 | 7 | const ScreenNoSleep = props => { 8 | const [wakeLockEnabled, toggleWakeLock] = useState(false) 9 | 10 | const toggleLock = () => { 11 | if (!wakeLockEnabled) { 12 | noSleep.enable(); 13 | toggleWakeLock(true) 14 | } else { 15 | noSleep.disable() 16 | toggleWakeLock(false) 17 | } 18 | } 19 | 20 | return ( 21 |
22 | 23 | 24 |

Screen lock/unlock functionality with NoSleep.js

25 | 28 | 29 | 30 | {wakeLockEnabled ? "Your device won't sleep now" : null} 31 | 32 |
33 | ) 34 | } 35 | 36 | export default ScreenNoSleep; -------------------------------------------------------------------------------- /src/pages/apis/Notifications.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const Notifications = props => { 5 | const [errorMessage, setErrorMessage] = useState('') 6 | 7 | const notify = () => { 8 | new Notification('Hi', { 9 | body: 'Body', 10 | }) 11 | } 12 | 13 | const showNotification = () => { 14 | if (!window.Notification) { 15 | setErrorMessage('This browser does not support notification API') 16 | } else { 17 | if (Notification.permission === 'granted') { 18 | notify() 19 | } else if (Notification.permission !== 'denied') { 20 | Notification.requestPermission() 21 | .then(permission => { 22 | if (permission === 'granted') { 23 | notify() 24 | } 25 | }) 26 | .catch(err => { 27 | setErrorMessage(err) 28 | }) 29 | } 30 | } 31 | } 32 | 33 | return ( 34 |
35 | 36 | 37 |

Notifications API

38 | 39 | 40 |
41 | {errorMessage && `Error: ${errorMessage}`} 42 |
43 |
44 | ) 45 | } 46 | 47 | export default Notifications; -------------------------------------------------------------------------------- /src/pages/apis/OnlineState.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useState, useEffect } from 'react' 3 | import Back from '../../components/Back' 4 | 5 | const OnlineState = props => { 6 | const [error, setError] = useState(null) 7 | const [isOnline, toggleIsOnline] = useState(true) 8 | 9 | const updateIsOnline = () => { 10 | toggleIsOnline(navigator.onLine); 11 | } 12 | 13 | useEffect(() => { 14 | if (!navigator.onLine) { 15 | setError('API not supported in your browser') 16 | } else { 17 | updateIsOnline(); 18 | window.addEventListener('online', updateIsOnline) 19 | window.addEventListener('offline', updateIsOnline) 20 | } 21 | 22 | return () => { 23 | window.removeEventListener('online', updateIsOnline) 24 | window.removeEventListener('offline', updateIsOnline) 25 | } 26 | }, []) 27 | 28 | return ( 29 |
30 | 31 | 32 |
33 | 34 | {error ?
{error}
:
You are {isOnline ? 'online' : 'offline'}
} 35 |
36 | ) 37 | } 38 | 39 | export default OnlineState; -------------------------------------------------------------------------------- /src/pages/apis/PageVisibility.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useState, useEffect } from 'react' 3 | import Back from '../../components/Back' 4 | 5 | const PageVisibility = props => { 6 | const [error, setError] = useState(null) 7 | const [visibilityMessage, setVisibilityMessage] = useState(null) 8 | 9 | const handleVisibilityChange = e => { 10 | setVisibilityMessage({ 11 | state: document.visibilityState, 12 | time: new Date(), 13 | }) 14 | } 15 | 16 | useEffect(() => { 17 | if (!document.visibilityState) { 18 | setError('Page Visibility API is not supported in your browser.') 19 | } else { 20 | document.addEventListener('visibilitychange', handleVisibilityChange) 21 | } 22 | 23 | return () => { 24 | if (document.visibilityState) { 25 | document.removeEventListener('visibilitychange', handleVisibilityChange) 26 | } 27 | } 28 | }, []) 29 | 30 | return ( 31 |
32 | 33 | 34 |
35 | 36 | {error ?
{error}
:
{`Page changed to ${visibilityMessage.state} at ${new Date()}`}
} 37 |
38 | ) 39 | } 40 | 41 | export default PageVisibility; -------------------------------------------------------------------------------- /src/pages/apis/PaymentRequest.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const PaymentRequestApi = props => { 5 | const [errorMessage, setErrorMessage] = useState('') 6 | const [paymentResponse, setPaymentResponse] = useState({}) 7 | const [paymentState, setPaymentState] = useState('') 8 | 9 | const getPaymentMethods = () => { 10 | const basicCardMethod = { 11 | supportedMethods: 'basic-card', 12 | data: { 13 | supportedNetworks: ['visa', 'mastercard'], 14 | supportedTypes: ['debit', 'credit'] 15 | } 16 | } 17 | 18 | const googlePayMethod = { 19 | supportedMethods: 'https://google.com/pay', 20 | data: { 21 | environment: 'TEST', 22 | apiVersion: 2, 23 | apiVersionMinor: 0, 24 | merchantInfo: { 25 | merchantId: '8220-9128-5411', 26 | merchantName: 'Takkar' 27 | }, 28 | allowedPaymentMethods: [{ 29 | type: 'CARD', 30 | parameters: { 31 | allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"], 32 | allowedCardNetworks: ["MASTERCARD", "VISA"] 33 | }, 34 | // tokenizationSpecification: { 35 | // type: 'PAYMENT_GATEWAY', 36 | // parameters: { 37 | // 'gateway': 'example', 38 | // 'gatewayMerchantId': 'exampleGatewayMerchantId' 39 | // } 40 | // } 41 | }] 42 | } 43 | } 44 | 45 | return [ 46 | basicCardMethod, 47 | // googlePayMethod 48 | ]; 49 | } 50 | 51 | const getPaymentDetails = () => { 52 | return { 53 | total: { 54 | label: 'Donation', 55 | amount: { 56 | currency: 'USD', 57 | value: '10' 58 | } 59 | }, 60 | // displayItems: [{ 61 | // label: 62 | // }] 63 | } 64 | } 65 | 66 | const getPaymentOptions = () => { 67 | return { 68 | requestPayerName: true, 69 | requestPayerEmail: true, 70 | // requestPayerPhone: true, 71 | }; 72 | } 73 | 74 | const processPayment = () => { 75 | return new Promise(resolve => setTimeout(() => resolve(true), 2000)) 76 | } 77 | 78 | const makePayment = async () => { 79 | try { 80 | const paymentRequest = new PaymentRequest(getPaymentMethods(), getPaymentDetails(), getPaymentOptions()) 81 | 82 | let canMakePayment = Promise.resolve(true) 83 | 84 | if (paymentRequest.canMakePayment) { 85 | canMakePayment = paymentRequest.canMakePayment() 86 | } 87 | 88 | if (await canMakePayment) { 89 | const paymentResponse = await paymentRequest.show() 90 | const processedPayment = await processPayment() 91 | 92 | if (processedPayment) { 93 | paymentResponse.complete('success') 94 | setPaymentState('success') 95 | } else { 96 | paymentResponse.complete('fail') 97 | setPaymentState('fail') 98 | } 99 | 100 | console.log(paymentResponse); 101 | setPaymentResponse(paymentResponse) 102 | } 103 | 104 | } catch (e) { 105 | setErrorMessage(e.message) 106 | } 107 | } 108 | 109 | return ( 110 |
111 | 112 |

PaymentRequest API

113 | 114 | 115 |
116 | 117 | {paymentState === 'success' 118 | ? Payment successfull 119 | : paymentState === 'fail' ? Payment failed : null} 120 | 121 | {paymentState === 'success' &&
122 |         {JSON.stringify(paymentResponse, undefined, 2)}
123 |       
} 124 | 125 |
{errorMessage}
126 |
127 | ) 128 | } 129 | 130 | export default PaymentRequestApi; 131 | -------------------------------------------------------------------------------- /src/pages/apis/PushNotifications.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import Back from '../../components/Back' 3 | import { urlB64ToUint8Array } from '../../helpers' 4 | import cookie from 'js-cookie' 5 | 6 | /* 7 | PUBLIC_KEY: BBDrHKLJfxVxcNKygxQzENzEb4mUPsuZrffu0EV6Hg_jyU5oR7LdG4bcrjHq7aWA5v8emlZpTMOIG68Wy5s8ofY 8 | PRIVATE_KEY: bK22-rLBe0ortsJ44q2HdB28TegnYMLoYkpKlOV0HfE 9 | */ 10 | 11 | const PUBLIC_KEY = 'BBDrHKLJfxVxcNKygxQzENzEb4mUPsuZrffu0EV6Hg_jyU5oR7LdG4bcrjHq7aWA5v8emlZpTMOIG68Wy5s8ofY' 12 | 13 | const API_DOMAIN = process.env.NODE_ENV === 'production' ? 'https://awesome-web-apis-server.herokuapp.com' : 'http://localhost:3001' 14 | 15 | const PushNotifications = props => { 16 | const [errorMessage, setErrorMessage] = useState('') 17 | const [subscription, setSubscription] = useState(null) 18 | const [subscriptionId, setSubscriptionId] = useState(cookie.get('subscription-id')) 19 | const swRegisteration = useRef(null) 20 | 21 | const getSubscription = async () => { 22 | try { 23 | const registeration = await navigator.serviceWorker.ready 24 | swRegisteration.current = registeration; 25 | const subscription = await registeration.pushManager.getSubscription() 26 | setSubscription(subscription) 27 | } catch (e) { 28 | setErrorMessage(e.message) 29 | } 30 | } 31 | 32 | useEffect(() => { 33 | if (Notification.permission === 'denied') { 34 | setSubscription(null) 35 | } else { 36 | getSubscription() 37 | } 38 | }, []) // eslint-disable-line 39 | 40 | const saveSubscription = (subscription) => { 41 | fetch(`${API_DOMAIN}/api/save-subscription`, { 42 | method: 'POST', 43 | headers: { 44 | 'Content-Type': 'application/json' 45 | }, 46 | body: JSON.stringify(subscription) 47 | }) 48 | .then(response => response.json()) 49 | .then(response => { 50 | setSubscription(subscription) 51 | cookie.set('subscription-id', response.data.id) 52 | setSubscriptionId(response.data.id) 53 | }) 54 | .catch(e => setErrorMessage(e.message)) 55 | } 56 | 57 | const subscribe = () => { 58 | const applicationServerKey = urlB64ToUint8Array(PUBLIC_KEY); 59 | swRegisteration.current.pushManager.subscribe({ 60 | userVisibleOnly: true, 61 | applicationServerKey 62 | }) 63 | .then(subscription => { 64 | saveSubscription(subscription) 65 | }) 66 | .catch(e => setErrorMessage(e.message)) 67 | } 68 | 69 | const unsubscribe = () => { 70 | try { 71 | fetch(`${API_DOMAIN}/api/delete-subscription`, { 72 | method: 'POST', 73 | headers: { 74 | 'Content-Type': 'application/json' 75 | }, 76 | body: JSON.stringify({ id: subscriptionId }) 77 | }) 78 | .then(resp => resp.json()) 79 | .then(resp => { 80 | console.log(resp) 81 | 82 | if (resp.data.success) { 83 | subscription.unsubscribe() 84 | cookie.remove('subscription-id') 85 | setSubscriptionId(null) 86 | setSubscription(null) 87 | } 88 | }) 89 | 90 | } catch (e) { 91 | setErrorMessage(e.message) 92 | } 93 | } 94 | 95 | const triggerNotification = () => { 96 | fetch(`${API_DOMAIN}/api/trigger-push-notification`, { 97 | method: 'POST', 98 | headers: { 99 | 'Content-Type': 'application/json' 100 | }, 101 | body: JSON.stringify({ id: subscriptionId }) 102 | }) 103 | .then(response => response.json()) 104 | .then(response => { 105 | console.log(response) 106 | }) 107 | .catch(e => setErrorMessage(e.message)) 108 | } 109 | 110 | return ( 111 |
112 | 113 | 114 |
115 |

Push Notifications API

116 | 117 | {/* setUserName(e.target.value)} /> */} 118 | 119 | {subscription 120 | ? 121 | : 122 | } 123 | 124 |
125 | 126 | {subscription &&
127 |
{JSON.stringify(subscription, null, 2)}
128 |
} 129 | 130 |
131 | {subscriptionId &&
132 | 133 |
} 134 | 135 |
136 |
137 | 138 |
{errorMessage}
139 | 140 |
141 |
142 | ) 143 | } 144 | 145 | export default PushNotifications; 146 | -------------------------------------------------------------------------------- /src/pages/apis/ScreenMedia.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react' 2 | import Back from '../../components/Back'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const ScreenMedia = props => { 6 | const [errorMessage, setErrorMessage] = useState('') 7 | const videoRef = useRef(null); 8 | const [mediaStream, setMediaStream] = useState(null) 9 | 10 | const startScreenShare = () => { 11 | if (navigator.mediaDevices.getDisplayMedia) { 12 | navigator.mediaDevices.getDisplayMedia({ 13 | video: { 14 | cursor: 'always', 15 | width: 420, 16 | height: 420 17 | }, 18 | audio: false, 19 | }) 20 | .then(mediaStream => { 21 | videoRef.current.srcObject = mediaStream; 22 | 23 | if (mediaStream) { 24 | setMediaStream(mediaStream) 25 | } 26 | 27 | videoRef.current.onloadedmetadata = () => { 28 | videoRef.current.play(); 29 | } 30 | }) 31 | .catch(e => setErrorMessage(e.message)) 32 | 33 | } else { 34 | setErrorMessage('MediaDevices.getDisplayMedia is not supported in your browser') 35 | } 36 | } 37 | 38 | const getVideoTrack = () => { 39 | const tracks = mediaStream?.getVideoTracks() || []; 40 | return tracks[0] 41 | } 42 | 43 | const getAudioTrack = () => { 44 | const tracks = mediaStream?.getAudioTracks() || []; 45 | return tracks[0] 46 | } 47 | 48 | const stopScreenShare = () => { 49 | const videoTrack = getVideoTrack() 50 | const audioTrack = getAudioTrack() 51 | if (videoTrack) { 52 | videoTrack.stop(); 53 | } 54 | if (audioTrack) { 55 | audioTrack.stop(); 56 | } 57 | videoRef.current.srcObject = null; 58 | setMediaStream(null) 59 | } 60 | 61 | return ( 62 |
63 | 64 | 65 |

We can use MediaDevice's getDisplayMedia API to capture user's screen media.

66 |
You can use getUserMedia's constraints/options also. Have a look at UserMedia for in-detail demo.
67 |
68 | 69 | {!!mediaStream 70 | ? 71 | : 72 | } 73 | 74 |
75 |
76 | 77 |
81 | ) 82 | } 83 | 84 | export default ScreenMedia; -------------------------------------------------------------------------------- /src/pages/apis/Share.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const ShareApi = props => { 5 | const [uploadedFiles, setUploadedFiles] = useState([]) 6 | 7 | const share = ({ title, url, text, files }) => { 8 | if (navigator.share) { 9 | navigator.share({ 10 | title, 11 | url, 12 | text, 13 | ...(files && { files }), 14 | }).then(resp => { 15 | console.log(resp) 16 | }).catch(e => { 17 | console.log(e) 18 | }) 19 | } 20 | } 21 | 22 | const shareUrl = () => { 23 | share({ 24 | title: 'ESPNcricinfo', 25 | url: 'https://www.espncricinfo.com/story/_/id/29304607/greatest-odi-batsman-all', 26 | text: 'Who is the greatest ODI batsman of all time?' 27 | }) 28 | } 29 | 30 | const updateFile = e => { 31 | console.log(e.target.files) 32 | setUploadedFiles(e.target.files) 33 | } 34 | 35 | const shareFiles = (files, title) => { 36 | console.log(files); 37 | if (navigator.canShare && navigator.canShare({ files })) { 38 | navigator.share({ 39 | files, 40 | title, 41 | }) 42 | .then(() => console.log('Share was successful.')) 43 | .catch((error) => console.log('Sharing failed', error)); 44 | } else { 45 | console.log(`Your system doesn't support sharing files.`); 46 | } 47 | } 48 | 49 | const shareUploadedFiles = () => { 50 | shareFiles(uploadedFiles, 'Your uploaded files') 51 | } 52 | 53 | const sharePhoto = async ({ fileUrl, name, title }) => { 54 | try { 55 | const response = await fetch(fileUrl); 56 | const blob = await response.blob(); 57 | const file = new File([blob], name, {type: blob.type}); 58 | 59 | share({ 60 | url: 'https://awesome-web-apis.surge.sh/api/share', 61 | title, 62 | files: [file] 63 | }); 64 | } catch (err) { 65 | console.log(err.name, err.message); 66 | } 67 | } 68 | 69 | return ( 70 |
71 | 72 | 73 |

Share API

74 | 75 |

Share URL

76 | 77 |
78 | 79 |

Share uploaded files

80 | 81 | {uploadedFiles.length ? : null} 82 |
83 | 84 |

Taj Mahal

88 |
89 | Taj Mahal 1 90 | 96 | 97 | Taj Mahal 2 98 | 104 |
105 |
106 | ) 107 | } 108 | 109 | export default ShareApi; -------------------------------------------------------------------------------- /src/pages/apis/ShareTarget.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const Profile = props => { 5 | const [title, setTitle] = useState('') 6 | const [description, setDescription] = useState('') 7 | const [url, setUrl] = useState('') 8 | 9 | useEffect(() => { 10 | const { searchParams } = new URL(window.location); 11 | setTitle(searchParams.get('title')) 12 | setDescription(searchParams.get('description')) 13 | setUrl(searchParams.get('url')) 14 | }, []) 15 | 16 | return ( 17 |
18 | 19 |

Share Target API

20 |
We can use this API to receive 'shares'. Goto any page on Chrome and tap on Share option.
21 | 22 |
23 | 24 | {(title || description || url) ? 25 |
26 |
Title shared: {title}
27 |
Description shared: {description}
28 |
URL shared: {url}
29 |
30 | : Shortcuts 31 | } 32 | 33 |
34 | ) 35 | } 36 | 37 | export default Profile; -------------------------------------------------------------------------------- /src/pages/apis/StorageQuota.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Back from '../../components/Back' 3 | import { formatBytes } from '../../helpers' 4 | 5 | const StorageQuota = props => { 6 | const [error, setError] = useState(null) 7 | const [storageInfo, setStorageInfo] = useState({ 8 | quota: null, 9 | usage: null, 10 | }) 11 | 12 | useEffect(() => { 13 | if (!navigator.storage.estimate) { 14 | setError('API is not supported in your browser') 15 | } else { 16 | navigator.storage.estimate() 17 | .then(storageEstimate => { 18 | console.log(storageEstimate) 19 | setStorageInfo({ 20 | quota: storageEstimate.quota, 21 | usage: storageEstimate.usage, 22 | }) 23 | }) 24 | .catch(e => setError(e.message)) 25 | } 26 | }, []) 27 | 28 | return ( 29 |
30 | 31 | 32 |
33 | 34 | {storageInfo.quota ?
35 |
Estimated quota: {formatBytes(storageInfo.quota)}
36 |
Estimated usage: {formatBytes(storageInfo.usage)}
37 |
Estimated usage in percent: {(storageInfo.usage * 100 / storageInfo.quota).toFixed(4)}%
38 |
: null} 39 | 40 |
{error}
41 |
42 | ) 43 | } 44 | 45 | export default StorageQuota; -------------------------------------------------------------------------------- /src/pages/apis/TextFragments.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import Back from '../../components/Back'; 3 | 4 | const TextFragments = props => { 5 | return ( 6 |
7 | 8 | 9 |

Text Fragments

10 |
When you click on below links to navigate, a piece of text will be highlighted.

11 | "ECMAScript Modules in Web Workers"
16 | TextStart and TextEnd 21 |
22 | ) 23 | } 24 | 25 | export default TextFragments; -------------------------------------------------------------------------------- /src/pages/apis/UserMedia.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const defaultVideoOptions = { 5 | width: 360, 6 | height: 360, 7 | cameraMode: 'front', 8 | } 9 | 10 | const defaultAudioOptions = { 11 | volume: 1, 12 | } 13 | 14 | const UserMedia = props => { 15 | const videoRef = useRef(null) 16 | const [errorMessage, setErrorMessage] = useState('') 17 | 18 | const [videoOptions, setVideoOptions] = useState(defaultVideoOptions) 19 | const [audioOptions, setAudioOptions] = useState(defaultAudioOptions) 20 | 21 | const [mediaStream, setMediaStream] = useState(null) 22 | 23 | const getVideoConstraints = () => { 24 | return { 25 | width: videoOptions.width, 26 | height: videoOptions.height, 27 | facingMode: videoOptions.cameraMode === 'front' ? "user" : "environment", 28 | } 29 | } 30 | 31 | const getAudioConstraints = () => { 32 | return { 33 | volume: audioOptions.volume, 34 | } 35 | } 36 | 37 | const startLivestream = () => { 38 | if (navigator.mediaDevices?.getUserMedia) { 39 | navigator.mediaDevices.getUserMedia({ 40 | video: getVideoConstraints(), 41 | audio: getAudioConstraints(), 42 | }) 43 | .then(mediaStream => { 44 | videoRef.current.srcObject = mediaStream; 45 | 46 | if (mediaStream) { 47 | setMediaStream(mediaStream) 48 | } 49 | 50 | videoRef.current.onloadedmetadata = () => { 51 | videoRef.current.play(); 52 | } 53 | }) 54 | .catch(e => setErrorMessage(e.message)) 55 | 56 | } else { 57 | setErrorMessage('MediaDevices.getUserMedia is not supported in your browser') 58 | } 59 | } 60 | 61 | const getVideoTrack = () => { 62 | const tracks = mediaStream?.getVideoTracks() || []; 63 | return tracks[0] 64 | } 65 | 66 | const getAudioTrack = () => { 67 | const tracks = mediaStream?.getAudioTracks() || []; 68 | return tracks[0] 69 | } 70 | 71 | const stopLivestream = () => { 72 | const videoTrack = getVideoTrack() 73 | const audioTrack = getAudioTrack() 74 | if (videoTrack) { 75 | videoTrack.stop(); 76 | } 77 | if (audioTrack) { 78 | audioTrack.stop(); 79 | } 80 | videoRef.current.srcObject = null; 81 | setMediaStream(null) 82 | setAudioOptions(defaultAudioOptions) 83 | setVideoOptions(defaultVideoOptions) 84 | } 85 | 86 | useEffect(() => { 87 | const videoTrack = getVideoTrack() 88 | const audioTrack = getAudioTrack(); 89 | 90 | if (videoTrack) { 91 | videoTrack.applyConstraints(getVideoConstraints()) 92 | } 93 | if (audioTrack) { 94 | audioTrack.applyConstraints(getAudioConstraints()) 95 | } 96 | 97 | // constraints.volume is deprecated and not recommended to use. Hence, updating video's volume. 98 | videoRef.current.volume = audioOptions.volume; 99 | }, [videoOptions, audioOptions]) // eslint-disable-line 100 | 101 | const handleVideoSize = e => { 102 | const [width, height] = e.target.value.split('x') 103 | setVideoOptions({ 104 | ...videoOptions, 105 | width: Number(width), 106 | height: Number(height) 107 | }) 108 | } 109 | 110 | const handleCameraOptions = e => { 111 | setVideoOptions({ 112 | ...videoOptions, 113 | cameraMode: e.target.value 114 | }) 115 | } 116 | 117 | const toggleVolume = () => { 118 | setAudioOptions({ 119 | ...audioOptions, 120 | volume: audioOptions.volume === 0 ? 1 : 0 121 | }) 122 | } 123 | 124 | const handleVolumeChange = e => { 125 | setAudioOptions({ 126 | ...audioOptions, 127 | volume: Number(e.target.value) 128 | }) 129 | } 130 | 131 | return ( 132 |
133 | 134 | 135 |

We can use MediaDevice's getUserMedia API to capture user's media.

136 | 137 | Video settings:   138 |
139 | Video size: 140 | {' '} 144 |
145 |
146 | Camera mode: 147 | 151 |
152 | 153 |
154 | Audio settings:   155 |
156 | Mute: 157 | 158 |
159 | Volume: 160 | 168 | 169 |

170 | {!mediaStream 171 | ? 172 | : 173 | } 174 | 175 |
176 |
177 | 178 |
182 | ) 183 | } 184 | 185 | export default UserMedia; -------------------------------------------------------------------------------- /src/pages/apis/Virate.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Back from '../../components/Back'; 3 | 4 | const Vibrate = props => { 5 | const vibrate = () => { 6 | if (navigator.vibrate) { 7 | // window.navigator.vibrate(200); 8 | navigator.vibrate([100,30,100,30,100,30,200,30,200,30,200,30,100,30,100,30,100]); // Vibrate 'SOS' in Morse. 9 | } 10 | } 11 | 12 | return ( 13 |
14 | 15 | 16 |

Vibration API

17 | 18 | 19 |
20 | ) 21 | } 22 | 23 | export default Vibrate; -------------------------------------------------------------------------------- /src/pages/apis/WakeLock.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const WakeLockApi = props => { 5 | const wakeLock = useRef(null) 6 | const [wakeLockEnabled, setWakeLockEnabled] = useState(false) 7 | const [errorMessage, setErrorMessage] = useState('') 8 | 9 | const enableWakeLock = async () => { 10 | if ('WakeLock' in window && 'request' in window.WakeLock) { 11 | try { 12 | wakeLock.current = await navigator.wakeLock.request('screen'); 13 | console.log('Screen Wake Lock is active'); 14 | setWakeLockEnabled(true) 15 | } catch (err) { 16 | setErrorMessage(`${err.name}, ${err.message}`); 17 | setWakeLockEnabled(false) 18 | } 19 | } else { 20 | setErrorMessage('WakeLock API is not supported in your browser!') 21 | } 22 | } 23 | 24 | const disableWakeLock = () => { 25 | wakeLock.current.release(); 26 | setWakeLockEnabled(false) 27 | } 28 | 29 | useEffect(() => { 30 | if (wakeLock.current) { 31 | wakeLock.current.addEventListener('release', () => { 32 | console.log('Screen Wake Lock was released'); 33 | setWakeLockEnabled(false) 34 | }); 35 | } 36 | }, [wakeLockEnabled]) 37 | 38 | return ( 39 |
40 | 41 | 42 |

Wake lock API

43 | 44 | {wakeLockEnabled 45 | ? 46 | : 47 | } 48 | 49 | 50 | {wakeLockEnabled ? "Your device won't sleep now" : null} 51 | 52 | 53 |
54 | {errorMessage && `Error: ${errorMessage}`} 55 |
56 |
57 | ) 58 | } 59 | 60 | export default WakeLockApi; -------------------------------------------------------------------------------- /src/pages/others/About.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const About = props => { 5 | return ( 6 |
7 | 8 | 9 |

The page was created specifically for app shortcuts.

10 | 11 | Shortcuts 12 |
13 | ) 14 | } 15 | 16 | export default About; -------------------------------------------------------------------------------- /src/pages/others/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from '../../components/Header' 3 | 4 | const Home = props => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 |
12 | Note: Please access the app on a mobile browser and make sure it's modern. I'd suggest using Chrome Canary. 13 |
14 | 15 |
16 |
17 | ) 18 | } 19 | 20 | export default Home; 21 | -------------------------------------------------------------------------------- /src/pages/others/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Back from '../../components/Back' 3 | 4 | const Profile = props => { 5 | return ( 6 |
7 | 8 | 9 |

The page was created specifically for app shortcuts.

10 | 11 | Shortcuts 12 |
13 | ) 14 | } 15 | 16 | export default Profile; -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if ('serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/sw.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | 62 | // console.log(registration); 63 | 64 | registration.onupdatefound = () => { 65 | const installingWorker = registration.installing; 66 | if (installingWorker == null) { 67 | return; 68 | } 69 | installingWorker.onstatechange = () => { 70 | if (installingWorker.state === 'installed') { 71 | if (navigator.serviceWorker.controller) { 72 | // At this point, the updated precached content has been fetched, 73 | // but the previous service worker will still serve the older 74 | // content until all client tabs are closed. 75 | console.log( 76 | 'New content is available and will be used when all ' + 77 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 78 | ); 79 | 80 | // Execute callback 81 | if (config && config.onUpdate) { 82 | config.onUpdate(registration); 83 | } 84 | } else { 85 | // At this point, everything has been precached. 86 | // It's the perfect time to display a 87 | // "Content is cached for offline use." message. 88 | console.log('Content is cached for offline use.'); 89 | 90 | // Execute callback 91 | if (config && config.onSuccess) { 92 | config.onSuccess(registration); 93 | } 94 | } 95 | } 96 | }; 97 | }; 98 | }) 99 | .catch(error => { 100 | console.error('Error during service worker registration:', error); 101 | }); 102 | } 103 | 104 | function checkValidServiceWorker(swUrl, config) { 105 | // Check if the service worker can be found. If it can't reload the page. 106 | fetch(swUrl, { 107 | headers: { 'Service-Worker': 'script' }, 108 | }) 109 | .then(response => { 110 | // Ensure service worker exists, and that we really are getting a JS file. 111 | const contentType = response.headers.get('content-type'); 112 | if ( 113 | response.status === 404 || 114 | (contentType != null && contentType.indexOf('javascript') === -1) 115 | ) { 116 | // No service worker found. Probably a different app. Reload the page. 117 | navigator.serviceWorker.ready.then(registration => { 118 | registration.unregister().then(() => { 119 | window.location.reload(); 120 | }); 121 | }); 122 | } else { 123 | // Service worker found. Proceed as normal. 124 | registerValidSW(swUrl, config); 125 | } 126 | }) 127 | .catch(() => { 128 | console.log( 129 | 'No internet connection found. App is running in offline mode.' 130 | ); 131 | }); 132 | } 133 | 134 | export function unregister() { 135 | if ('serviceWorker' in navigator) { 136 | navigator.serviceWorker.ready 137 | .then(registration => { 138 | registration.unregister(); 139 | }) 140 | .catch(error => { 141 | console.error(error.message); 142 | }); 143 | } 144 | } 145 | --------------------------------------------------------------------------------