├── .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 | You need to enable JavaScript to run this app.
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 |
67 |
68 |
You're Offline
69 |
70 |
No Internet connection found. Check your connection or try again
71 |
72 |
Try Again
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 | {/* */}
83 | {/* */}
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 |
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 |
Copy
48 |
Paste
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
Copy the above image
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 |
Fetch contacts
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
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 | Save
67 | Save As
68 |
69 |
70 | )
71 | }
72 |
73 | return (
74 |
75 |
76 |
77 |
78 |
79 |
80 | Open file
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 |
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 |
Get current position
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 | ?
Start Idle detection
48 | :
Stop Idle detection
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 |
Install app
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 |
Play audio
154 |
155 |
156 |
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 |
26 | {wakeLockEnabled ? 'Disable wake lock' : 'Enable wake lock'}
27 |
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 |
Show notification
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 |
Donate $10
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 | ?
Unsubscribe from push notifications
121 | :
Subscribe to push notifications
122 | }
123 |
124 |
125 |
126 | {subscription &&
127 |
{JSON.stringify(subscription, null, 2)}
128 |
}
129 |
130 |
131 | {subscriptionId &&
132 | Trigger a push notification
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 | ?
Stop screen share
71 | :
Start screen share
72 | }
73 |
74 |
75 |
76 |
77 |
78 |
79 |
{errorMessage}
80 |
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 |
Wanna share an interesting CricInfo article?
77 |
78 |
79 |
Share uploaded files
80 |
81 | {uploadedFiles.length ?
Share uploaded files? : null}
82 |
83 |
84 |
Taj Mahal
88 |
89 |
90 |
sharePhoto({
91 | fileUrl: 'https://cra-workbox.surge.sh/taj-mahal-1.jpg',
92 | title: 'TajMahal drone view',
93 | name: 'taj-mahal-drone-view.jpg'
94 | })}
95 | >Share
96 |
97 |
98 |
sharePhoto({
99 | fileUrl: 'https://cra-workbox.surge.sh/taj-mahal-2.jpg',
100 | title: 'TajMahal front view',
101 | name: 'taj-mahal-front-view.jpg'
102 | })}
103 | >Share
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 | :
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 |
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 |
141 | 1280x720
142 | 360x360
143 | {' '}
144 |
145 |
146 | Camera mode:
147 |
148 | Front
149 | Back
150 |
151 |
152 |
153 |
154 |
Audio settings:
155 |
156 | Mute:
157 | {audioOptions.volume === 0 ? 'Unmute' : 'Mute'}
158 |
159 |
Volume:
160 |
161 | 0%
162 | 20%
163 | 40%
164 | 60%
165 | 80%
166 | 100%
167 |
168 |
169 |
170 | {!mediaStream
171 | ?
Start Livestream
172 | :
Stop Livestream
173 | }
174 |
175 |
176 |
177 |
178 |
179 |
180 |
{errorMessage}
181 |
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 | Vibrate
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 | ?
Disable wake lock
46 | :
Enable wake lock
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 |
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 |
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 |
--------------------------------------------------------------------------------