├── LICENSE.md ├── README.md ├── disco-db-demo ├── .eslintrc.json ├── .gitignore ├── README.md ├── __mocks__ │ ├── fileMock.js │ └── styleMock.js ├── __tests__ │ ├── __snapshots__ │ │ └── snapshot.js.snap │ ├── e2e │ │ └── example.spec.ts │ ├── index.test.jsx │ ├── server │ │ └── authController.js │ ├── snapshot.js │ ├── supertest.js │ └── sw-test.js ├── babel.config.js ├── components │ ├── Navbar.js │ ├── Notes.js │ ├── Sidebar.js │ ├── Weather.js │ ├── WeatherDisplay.js │ ├── layout.js │ └── weatherAPI.js ├── jest-setup.js ├── jest-teardown.js ├── jest.config.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── _app.js │ ├── about.js │ ├── api │ │ └── hello.js │ ├── auth │ │ ├── login.js │ │ └── signup.js │ ├── index.js │ └── user │ │ ├── index.js │ │ └── notes.js ├── public │ ├── backgroundSync.js │ ├── configMap.js │ ├── discoGlobals.js │ ├── discoSync.js │ ├── discodb.config.js │ ├── favicon.ico │ ├── idbOperations.js │ ├── swCacheSite-indexedDB.js │ └── vercel.svg ├── server │ ├── controllers │ │ ├── authController.js │ │ ├── userController.js │ │ └── weatherController.js │ ├── models │ │ ├── dbConnection.js │ │ ├── model.js │ │ └── responseModel.js │ ├── routers │ │ ├── apiRouter.js │ │ ├── authRouter.js │ │ └── userRouter.js │ └── server.js └── styles │ ├── Home.module.css │ └── globals.css ├── discodb ├── LICENSE.md ├── README.md ├── discoFunctions │ ├── backgroundSync.js │ ├── discoGlobals.js │ ├── discoSync.js │ └── idbOperations.js ├── index.js ├── package-lock.json └── package.json ├── package-lock.json └── package.json /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | DiscoDB Logo 3 |

4 | 5 | # DiscoDB: 6 | 7 | ## Table of Contents 8 | 9 | - [Features](#features) 10 | 11 | - [Installation](#installation) 12 | 13 | - [How It works](#how-it-works) 14 | 15 | - [Demo Apps](#demo-app) 16 | 17 | - [Contributing](#contributing) 18 | 19 | - [Authors](#authors) 20 | 21 | - [License](#license) 22 | 23 | ## Features 24 | 25 | - A minimalist IndexedDB (IDB) wrapper and syncing solution for when your application disco(nnects) from the network. 26 | - Lightweight with zero dependencies. 27 | - Functionalities can be implemented via Service Workers with minimal modification to client side code 28 | - Supports syncing IDB with NoSQL databases (MongoDB) with a unique keypath 29 | - Promise based wrapper for IDB to perform local CRUD operations while offline to provide seamless UX 30 | - Sync to main database via action queue with automatic network availability detection. 31 | 32 |
33 |
34 | Offline Capabilities 35 | 36 | ![](https://discodb.dev/wp-content/uploads/2022/03/Offline_capability2.gif) 37 |
38 | 39 |
40 | Dynamic Offline Data 41 | 42 | ![](https://discodb.dev/wp-content/uploads/2022/03/indexedDB_update.gif) 43 |
44 | 45 |
46 | Custom Action Queue 47 | 48 | ![](https://discodb.dev/wp-content/uploads/2022/03/actionqueue.gif) 49 |
50 |
51 | 52 | ## Installation 53 | 54 | ```bash 55 | npm install disco-db 56 | ``` 57 | ### Import the Library into the Service Worker 58 | Assuming a bundler such as Webpack, Rollup, etc is used: 59 | ```js 60 | import { discoConnect, discoSyncToServer, discoSyncOffline, discoSyncOnline, onlineUrlArr, offlineUrlArr, dbGlobals, idbPromise } from 'discodb'; 61 | ``` 62 | When registering the service worker, pass in an option object with property ``type:'module'`` 63 | ```js 64 | if("serviceWorker" in navigator) { 65 | window.addEventListener("load", function () { 66 | navigator.serviceWorker.register("sw.js", {type: 'module'}) 67 | ``` 68 | ## How It works 69 | 70 | ### Setting up the Config File 71 | Our library requires some minimal configuration. In the root directory of the project, create a file labeled ``discodb.config.js`` and update the values of the corresponding key property. 72 | ```js 73 | // discodb.config.js 74 | 75 | const dbGlobals = 76 | { 77 | version: "IDB version", 78 | databaseName: "IDB database name", 79 | storeName: "IDB Object Store name", 80 | syncQueue: "IDB Object Store Queue name", 81 | keypath: "Primary key of main database table", 82 | // Add all routes to be intercepted 83 | onlineRoutes: [ 84 | { 85 | url: "", 86 | } 87 | ], 88 | offlineRoutes: [ 89 | { 90 | url: " ", 91 | }, 92 | { 93 | url: " ", 94 | } 95 | ] 96 | } 97 | 98 | export { dbGlobals }; 99 | 100 | ``` 101 | 102 | ### DiscoSyncOffline & DiscoSyncOnline 103 | 104 | ``discoSyncOffline(method, url, clonedRequest)`` 105 | 106 | 1. ``discoSyncOffline`` is a request reducer that intercepts fetch requests and implements CRUD operations on the passed in endpoints. 107 | 108 | 2. ``discoSyncOffline`` takes in three parameters, the method and url of the ``event.request`` as well as a clone of the ``event.request``, utilizing the ``.clone()`` method. 109 | 110 | 3. Under the hood, ``discoSyncOffline`` will check the url, and perform a **GET**, **DELETE**, or **PATCH** operation with indexedDB and return a new **Response** object back to the client. 111 | 112 | 113 | ``discoSyncOnline(method, url, clonedResponse)`` 114 | 115 | 1. ``discoSyncOnline`` establishes a connection to indexedDB and populates the object store with your noSQL data. 116 | 117 | 2. ``discoSyncOnline`` takes in three paramaters, the method and url of the ``event.request`` as well as a clone of the ``response`` that was sent back from the server. 118 | 119 | 3. Under the hood, ``discoSyncOnline`` will check the url passed in and first clear indexedDB of any stale data, and repopulate with the response body that it received back from the server. 120 | 121 | 122 | ### Initializing IndexedDB Database 123 | To initialize an idb with attributes passed into ```discodb.config.js```, invoke ``` discoConnect()``` when installing the service worker 124 | ```js 125 | self.addEventListener('install', event => { 126 | discoConnect(); 127 | }); 128 | ``` 129 | 130 | ### Intercepting Event Requests 131 | ```discoSyncOffline()``` and ```discoSyncOnline()``` require the following to be true: 132 | 1. Network falling back to cache Service Worker strategy: [Documentation](https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker) 133 | 2. Response and Request objects passed into ```discoSyncOffline()``` and ```discoSyncOnline()``` respectively will need to be a cloned version. 134 | 135 | ```onlineUrlArr``` and ```offlineUrlArr``` will include the ```onlineRoutes``` and ```offlineRoutes``` from ```discodb.config.js ``` respectively in an Array. 136 | ```js 137 | self.addEventListener('fetch', event => { 138 | // clone the request 139 | const reqClone = event.request.clone(); 140 | event.respondWith( 141 | // network first approach 142 | fetch(event.request) 143 | .then((response) => { 144 | // clone the response 145 | const resCloneCache = response.clone(); 146 | const resCloneDB = response.clone() 147 | // open caches and store resCloneCache 148 | // ... 149 | // intercept routes included in onlineUrlArr 150 | if (onlineUrlArr.includes(url)){ 151 | discoSyncOnline(method, url, resCloneDB); 152 | } 153 | return response; 154 | }) 155 | // Fallback to Cache 156 | .catch((err) => { 157 | //invoke offline reducer to perform RUD functions to indexedDB 158 | if (offlineUrlArr.includes(url)){ 159 | return discoSyncOffline(method, url, reqClone); 160 | } 161 | // return cache 162 | // ... 163 | }) 164 | ) 165 | }); 166 | ``` 167 | 168 | ### Implementing the Action Queue Synchonization 169 | You can also use the synchronization queue separately from our reducers! Make sure to ``discoConnect()`` to the IDB database and have your configuration file ready. 170 | 1. Set up an event handler to listen for "***sync***" and a conditional to catch our tag, "***discoSync***". 171 | 1. Request for a synchronization event by registering through the service worker through ``discoRegisterSync()``. By default, the sync tag assigned to the request is '***discoSync***'. 172 | 1. Once a sync request has been registered, we now can add an object containing the HTTP request to the Object Store Queue by invoking ``discoAddToQueue(object)``.
173 | * Object format must contain these properties 174 | ```js 175 | { 176 | url: "Route URL", 177 | method: "HTTP Method", 178 | body: "data object from HTTP request" 179 | } 180 | ``` 181 | 1. Now that the Object Store Queue has been populated with HTTP requests, we can send them to the main server! Within the event handler for sync, invoke ``discoSyncToServer()``. 182 | ```js 183 | self.addEventListener('sync', (event) => { 184 | if(event.tag === 'discoSync'){ 185 | discoSyncToServer(); 186 | }; 187 | }; 188 | ``` 189 | 190 | ## Demo App 191 | Our demostration application utilizing this library is a simple note taking app. The demo app relies on service workers intercepting all HTTP requests for both online and offline requests. 192 | 193 | Fork and clone our repository onto your local repository and follow the README in the app. 194 | 195 | ## Contributing 196 | 197 | We'd love for you to test this library out and submit any issues you encounter. Also feel free to fork to your own repo and submit pull requests! 198 | 199 | ## Authors 200 |
201 | 202 | [Eric Gomez](https://github.com/ergomez0201) | [LinkedIn](https://www.linkedin.com/in/eric-gomez/) 203 |
204 | 205 | [Eric McCorkle](https://github.com/ericmccorkle) | [LinkedIn](https://www.linkedin.com/in/eric-mccorkle/) 206 |
207 | 208 | [Jackson Tong](https://github.com/jacksonktong) | [LinkedIn](www.linkedin.com/in/jacksonktong) 209 |
210 | 211 | [Young Min Lee](https://github.com/youngmineeh) | [LinkedIn](www.linkedin.com/in/youngminlee-) 212 | 213 |
214 | 215 | ## License 216 | 217 | DiscoDB is [MIT licensed](https://github.com/oslabs-beta/discodb/blob/main/LICENSE.md). 218 | -------------------------------------------------------------------------------- /disco-db-demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | // "extends": "next/core-web-vitals" 3 | "extends": ["next/babel","next/core-web-vitals"] 4 | } 5 | -------------------------------------------------------------------------------- /disco-db-demo/.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | .env 33 | 34 | # vercel 35 | .vercel 36 | 37 | ./DiscoDB/disco-db-demo/server/models/testQuery.js -------------------------------------------------------------------------------- /disco-db-demo/README.md: -------------------------------------------------------------------------------- 1 | Things to Note in our Readme 2 | - Offline sync queue currently maxes out at 50 requests (validate this) 3 | 4 | 5 | //Config file// 6 | version: config required 7 | databaseName: default to 'database', but user should specify. Default values may be more difficult to update functions with. Should require it 8 | storeName: default to 'objectStore', but user should specify. Default values may be more difficult to update functions with. Should require it 9 | syncQueue: default to 'Queue', but user should specify. Default values may be more difficult to update functions with. Should require it 10 | keyPath: required, user needs to specify 11 | 12 | 13 | 14 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 15 | 16 | ## Getting Started 17 | 18 | First, run the development server: 19 | 20 | ```bash 21 | npm run dev 22 | # or 23 | yarn dev 24 | ``` 25 | 26 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 27 | 28 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 29 | 30 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 31 | 32 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 33 | 34 | ## Learn More 35 | 36 | To learn more about Next.js, take a look at the following resources: 37 | 38 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 39 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 40 | 41 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 42 | 43 | ## Deploy on Vercel 44 | 45 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 46 | 47 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 48 | -------------------------------------------------------------------------------- /disco-db-demo/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub' 2 | // If you're running into the issue "Failed to parse src "test-file-stub" on 'next/image'", add a '/' to your fileMock. -------------------------------------------------------------------------------- /disco-db-demo/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {} -------------------------------------------------------------------------------- /disco-db-demo/__tests__/__snapshots__/snapshot.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders homepage unchanged 1`] = ` 4 |
5 |
8 |
11 |

14 | Welcome to 15 | 18 | Next.js! 19 | 20 |

21 |

24 | Get started by editing 25 | 26 | 29 | pages/index.js 30 | 31 |

32 | 80 |
81 | 119 |
120 |
121 | `; 122 | -------------------------------------------------------------------------------- /disco-db-demo/__tests__/e2e/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test' 2 | 3 | test('should navigate to the about page', async ({ page }) => { 4 | // Start from the index page (the baseURL is set via the webServer in the playwright.config.ts) 5 | await page.goto('http://localhost:3000/') 6 | // Find an element with the text 'About Page' and click on it 7 | await page.click('text=About Page') 8 | // The new url should be "/about" (baseURL is used there) 9 | await expect(page).toHaveURL('http://localhost:3000/about') 10 | // The new page should contain an h1 with "About Page" 11 | await expect(page.locator('h1')).toContainText('About Page') 12 | }) -------------------------------------------------------------------------------- /disco-db-demo/__tests__/index.test.jsx: -------------------------------------------------------------------------------- 1 | // sample test from Next docs 2 | 3 | import { render, screen } from '@testing-library/react' 4 | import '@testing-library/jest-dom' 5 | import Home from '../pages/index' 6 | 7 | describe('Home', () => { 8 | it('renders a heading', () => { 9 | render() 10 | 11 | const heading = screen.getByRole('heading', { 12 | name: /welcome to next\.js!/i, 13 | }) 14 | 15 | expect(heading).toBeInTheDocument() 16 | }) 17 | }) -------------------------------------------------------------------------------- /disco-db-demo/__tests__/server/authController.js: -------------------------------------------------------------------------------- 1 | // import the exported module from ./server/controllers/authController.js 2 | import authController from '../../server/controllers/authController'; 3 | const db = require('../../server/models/dbConnection') 4 | // const mongoose = require('mongoose'); 5 | const bcrypt = require('bcrypt'); 6 | const { Users, Notes } = require('../../server/models/model'); 7 | 8 | 9 | describe('Auth Controller', () => { 10 | const deleteTestUsers = () => { 11 | Users.deleteOne({username: 'testUser1'}); 12 | Users.deleteOne({username: 'testUser2'}); 13 | } 14 | const setExistingUser = username => { 15 | return user1 = {username: username} 16 | } 17 | 18 | const mockRequest = (username, password) => { 19 | return {body: {username: username, password: password}} 20 | } 21 | 22 | const mockResponse = () => { 23 | const res = {}; 24 | res.status = jest.fn().mockReturnValue(res); 25 | res.json = jest.fn().mockReturnValue(res); 26 | return res; 27 | }; 28 | 29 | const encrypt = (password, workFactor) => { 30 | return bcrypt.hash(password, workFactor); 31 | } 32 | 33 | berforeAll(() => { 34 | db.connection(); 35 | deleteTestUsers(); 36 | const workFactor = 10; 37 | const username = 'testUser1'; 38 | const password = 'test123'; 39 | const encryptPW = encrypt(password, workFactor); 40 | }) 41 | 42 | // afterEach? afterAll? 43 | // drop the record(s) that were just created 44 | 45 | describe('Sign up ', async () => { 46 | // declare a test req.body object with a test username and password 47 | const req = mockRequest(username, password); 48 | const res = mockResponse(); 49 | await authController.signup(req, res) 50 | // it should hash the password 51 | it('Should hash the password', () => { 52 | expect(res.locals.password).toBe(encryptPW) 53 | }) 54 | // it should create the new user with password in the db 55 | // it should reject a username if it is not unique 56 | 57 | // after each 58 | // remove the user just created via Delete operation 59 | }); 60 | 61 | 62 | // before 63 | // create a test user 64 | 65 | // describe('Log in', ) 66 | // it should log in the test user 67 | // it should return the appropriate error message if password incorrect 68 | // it should return next() if password compare successful 69 | // it should return error if no matching username found 70 | 71 | // after 72 | // delete the test user 73 | }); -------------------------------------------------------------------------------- /disco-db-demo/__tests__/snapshot.js: -------------------------------------------------------------------------------- 1 | // sample test from Next docs 2 | 3 | import { render } from '@testing-library/react' 4 | import Home from '../pages/index' 5 | 6 | it('renders homepage unchanged', () => { 7 | const { container } = render() 8 | expect(container).toMatchSnapshot() 9 | }) -------------------------------------------------------------------------------- /disco-db-demo/__tests__/supertest.js: -------------------------------------------------------------------------------- 1 | const { resolveHref } = require('next/dist/shared/lib/router/router'); 2 | const request = require('supertest'); 3 | // const app = require('../server/server.js'); 4 | 5 | const app = 'http://localhost:3000'; 6 | 7 | // authRouter testing 8 | 9 | // /auth/login 10 | describe('userRouter /login testing', ()=>{ 11 | it('Loads the login page', async () => { 12 | await request(app) 13 | .get('/auth/login') 14 | .expect(200) 15 | }) 16 | 17 | // // 18 | // it('Successfully logs in a user', () => { 19 | // return request(app) 20 | // .post('/auth/login') 21 | // .send({username: 'test123', password: 'test123'}) 22 | // .expect(200) 23 | // .end((err, res) => { 24 | // if (err) { 25 | // reject(new Error ('An error occurred:', err)) 26 | // } 27 | // resolve(res.body) 28 | // }) 29 | // }) 30 | }) -------------------------------------------------------------------------------- /disco-db-demo/__tests__/sw-test.js: -------------------------------------------------------------------------------- 1 | const makeServiceWorkerEnv = require('service-worker-mock'); 2 | // const makeFetchMock = require('service-worker-mock/fetch'); 3 | require('cross-fetch/polyfill'); 4 | 5 | describe('Service worker', () => { 6 | beforeEach(() => { 7 | const serviceWorkerEnv = makeServiceWorkerEnv(); 8 | Object.defineProperty(serviceWorkerEnv, 'addEventListener', { 9 | value: serviceWorkerEnv.addEventListener, 10 | enumerable: true 11 | }); 12 | Object.assign(global, serviceWorkerEnv) 13 | // const fetchMock = makeFetchMock(); 14 | // Object.assign(global, fetchMock); 15 | jest.resetModules(); 16 | }); 17 | 18 | it('should add listeners', async () => { 19 | require('../public/swCacheSite-indexedDB.js'); 20 | await self.trigger('install'); 21 | expect(self.listeners.get('install')).toBeDefined(); 22 | expect(self.listeners.get('activate')).toBeDefined(); 23 | expect(self.listeners.get('fetch')).toBeDefined(); 24 | }); 25 | 26 | it('should delete old caches on activate', async () => { 27 | require('../public/swCacheSite-indexedDB.js'); 28 | 29 | // Create old cache 30 | await self.caches.open('cacheName'); 31 | expect(self.snapshot().caches.cacheName).toBeDefined(); 32 | 33 | // Activate and verify old cache is removed 34 | await self.trigger('activate'); 35 | expect(self.snapshot().caches.cacheName).toStrictEqual(undefined); 36 | }); 37 | 38 | it('should return a cached response', async () => { 39 | require('../public/swCacheSite-indexedDB.js'); 40 | 41 | const cachedResponse = { clone: () => { }, data: { key: 'value' } }; 42 | const cachedRequest = new Request('/test'); 43 | const cache = await self.caches.open('TEST'); 44 | cache.put(cachedRequest, cachedResponse); 45 | 46 | const response = await self.trigger('fetch', cachedRequest); 47 | expect(response.data.key).toEqual('value'); 48 | }); 49 | 50 | it('should fetch and cache an uncached request', async () => { 51 | const mockResponse = { clone: () => { return { data: { key: 'value' } } } }; 52 | global.fetch = (response) => Promise.resolve({ ...mockResponse, headers: response.headers }); 53 | 54 | require('../public/swCacheSite-indexedDB.js'); 55 | 56 | const request = new Request('/test'); 57 | const response = await self.trigger('fetch', request); 58 | expect(response.clone()).toEqual(mockResponse.clone()); 59 | 60 | const runtimeCache = self.snapshot().caches['my-site-cache-v3']; 61 | expect(runtimeCache[request.url]).toEqual(mockResponse.clone()); 62 | }); 63 | 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /disco-db-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | require.resolve('next/babel') 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /disco-db-demo/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AppBar from '@mui/material/AppBar'; 3 | import Box from '@mui/material/Box'; 4 | import Toolbar from '@mui/material/Toolbar'; 5 | import Typography from '@mui/material/Typography'; 6 | import Button from '@mui/material/Button'; 7 | import IconButton from '@mui/material/IconButton'; 8 | import MenuIcon from '@mui/icons-material/Menu'; 9 | import Switch from '@mui/material/Switch'; 10 | import FormControlLabel from '@mui/material/FormControlLabel'; 11 | import FormGroup from '@mui/material/FormGroup'; 12 | import { useRouter } from 'next/router'; 13 | 14 | export default function Navbar(props) { 15 | const router = useRouter(); 16 | 17 | const handleChange = (event) => { 18 | //will need to add logic for offline mode 19 | props.setOnline(event.target.checked); 20 | } 21 | 22 | //check for network connectivity 23 | window.addEventListener('offline', (event) => { 24 | props.setOnline(false); 25 | }) 26 | window.addEventListener('online', (event) => { 27 | props.setOnline(true); 28 | }) 29 | 30 | const handleLogout = (event) => { 31 | event.preventDefault(); 32 | //sends GET request to auth/logout 33 | 34 | const testURL = '/api/hello'; 35 | const devURL = '/auth/logout'; 36 | 37 | fetch(testURL) 38 | .then(response => response.json()) 39 | .then(data => { 40 | //clear out username in local storage when user logs off 41 | localStorage.clear(); 42 | router.push('/auth/login'); 43 | }) 44 | .catch(error => console.log('Error', error)); 45 | 46 | } 47 | return ( 48 | 49 | theme.zIndex.drawer + 1}}> 50 | 51 | 58 | 59 | 60 | 61 | Notes 62 | 63 | 64 | 72 | } 73 | label={props.online ? 'Online' : 'Offline'} 74 | /> 75 | 76 | 77 | 78 | 79 | 80 | ); 81 | } -------------------------------------------------------------------------------- /disco-db-demo/components/Notes.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import TextField from '@mui/material/TextField'; 4 | import Box from '@mui/material/Box'; 5 | import Container from '@mui/material/Container'; 6 | import CssBaseline from '@mui/material/CssBaseline'; 7 | 8 | export default function NotesContainer(props) { 9 | 10 | const handleTitle = (event) => { 11 | props.setNewTitle(event.target.value); 12 | }; 13 | 14 | const handleContent = (event) => { 15 | props.setNewContent(event.target.value); 16 | } 17 | 18 | const formField = [ 19 | 20 | 28 | 36 | 37 | ] 38 | 39 | const handleSave = (event) => { 40 | event.preventDefault(); 41 | 42 | const noteTitle = document.querySelector('[name="noteTitle"]'); 43 | const noteContent = document.querySelector('[name="noteContent"]'); 44 | const saveBody = { 45 | //grab id from query params 46 | _id: props.noteID, 47 | title: noteTitle.value, 48 | content: noteContent.value, 49 | updatedAt: Date.now(), 50 | } 51 | 52 | const testURL = '/api/hello'; 53 | const devURL = '/user/notes'; 54 | fetch(devURL, { 55 | method: 'PATCH', 56 | headers: { 57 | 'Content-Type': 'application/json', 58 | }, 59 | body: JSON.stringify(saveBody), 60 | }) 61 | .then(res => res.json()) 62 | .then(data => { 63 | props.setRefresh(true); 64 | }) 65 | .catch((err) => { 66 | console.log('Error in patching notes:', err) 67 | } 68 | )}; 69 | 70 | const handleDelete = (event) => { 71 | event.preventDefault(); 72 | 73 | const deleteBody = { 74 | //grab id from query params 75 | _id: props.noteID, 76 | username: localStorage.user, 77 | } 78 | 79 | const testURL = '/api/hello'; 80 | const devURL = '/user/notes'; 81 | fetch(devURL, { 82 | method: 'DELETE', 83 | headers: { 84 | 'Content-Type': 'application/json', 85 | }, 86 | body: JSON.stringify(deleteBody), 87 | }) 88 | .then(res => res.json()) 89 | .then(data => { 90 | console.log('Success', data); 91 | //should remove entry from array 92 | 93 | props.setRefresh(true); 94 | }) 95 | .catch((err) => { 96 | console.log('Error in deleting notes:', err) 97 | props.setRefresh(true); 98 | }) 99 | }; 100 | 101 | return ( 102 | 103 | 104 | 105 | 106 | {formField} 107 | 108 | 112 | 115 | 116 | 117 | 118 | 119 | ) 120 | } 121 | -------------------------------------------------------------------------------- /disco-db-demo/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import Box from '@mui/material/Box'; 4 | import Drawer from '@mui/material/Drawer'; 5 | import Toolbar from '@mui/material/Toolbar'; 6 | import List from '@mui/material/List'; 7 | import Divider from '@mui/material/Divider'; 8 | import ListItem from '@mui/material/ListItem'; 9 | import ListItemText from '@mui/material/ListItemText'; 10 | import NotesIcon from '@mui/icons-material/Notes'; 11 | import { Button } from '@mui/material'; 12 | 13 | const drawerWidth = 240; 14 | 15 | export default function SideBar(props) { 16 | const router = useRouter(); 17 | const [setSidebar, setNewSidebar] = useState([]); 18 | //Saves all notes as buttons for user on front-end for later access. 19 | const sidebarArray = []; 20 | //On initial render, invoke useEffect to grab all notes on props pertaining to user. 21 | //Populate the notes in an array and update state to reflect. 22 | 23 | useEffect(() => { 24 | props.noteArray.forEach((ele) => { 25 | //usernote has entire object per note for user 26 | const userNoteButton = 27 | 28 | 29 | 30 | //Convert each usernote into a button and push in array for useState. 31 | sidebarArray.push(userNoteButton); 32 | }) 33 | setNewSidebar([sidebarArray]) 34 | }, [props.noteArray]) 35 | 36 | function newNoteHandler(){ 37 | //POST request to user/notes with object of {username: username, createdAt: unix time} 38 | //Expect response of res.locals.data = {_id:id} 39 | //Poplate note array with a new icon with unique ID 40 | const newNoteInfo = { 41 | //Placeholder username, need to replace 42 | username: localStorage.getItem('user'), 43 | createdAt: Date.now() 44 | } 45 | const testURL = '/api/hello'; 46 | const devURL = '/user/notes'; 47 | fetch(devURL, { 48 | method: 'POST', 49 | headers: { 50 | 'Content-Type': 'application/json', 51 | }, 52 | body: JSON.stringify(newNoteInfo), 53 | }) 54 | .then(res => res.json()) 55 | .then((data) => { 56 | const uniqId = data.data._id; 57 | const newNote = 58 | 59 | 60 | 61 | 62 | setNewSidebar([...setSidebar, newNote]) 63 | props.setRefresh(true); 64 | }) 65 | .catch((err) => { 66 | console.log('Error in creating new note:', err) 67 | }); 68 | }; 69 | //Click handler to obtain ID attribute and shallow route to the note. 70 | function currNoteHandler (e){ 71 | const targetId = e.currentTarget.id 72 | router.push(`/user/notes?${targetId}`, undefined, {shallow: true}); 73 | } 74 | 75 | return ( 76 | 77 | 89 | 90 | 91 | 92 | 93 | 94 | {setSidebar} 95 | 96 | 97 | 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /disco-db-demo/components/Weather.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Grid from '@mui/material/Grid'; 3 | import Card from '@mui/material/Card'; 4 | import CardContent from '@mui/material/CardContent'; 5 | import TextField from '@mui/material/TextField'; 6 | import WeatherAPI from './weatherAPI'; 7 | import Typography from '@mui/material/Typography'; 8 | 9 | 10 | export default function Weather() { 11 | const [city, setCity] = React.useState(null); 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | Weather Report 19 | 20 | setCity(event.target.value)} 24 | /> 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /disco-db-demo/components/WeatherDisplay.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Typography from '@mui/material/Typography'; 3 | import CardContent from '@mui/material/CardContent'; 4 | import Box from '@mui/material/Box'; 5 | 6 | export default function WeatherDisplay(props) { 7 | const weatherReport = props.weatherReport; 8 | const weatherMain = weatherReport.weather[0].main; 9 | const weatherDescription = weatherReport.weather[0].description; 10 | const temp = weatherReport.main.temp; 11 | const humidity = weatherReport.main.humidity; 12 | const wind = weatherReport.wind.speed; 13 | const country = weatherReport.sys.country; 14 | const city = weatherReport.name; 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | {city}, {country} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Temp: {temp} 32 | ° 33 | {"F"} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {weatherDescription} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Humidity: {humidity} % 52 | 53 | 54 | 55 | 56 | Wind: {wind} mph 57 | 58 | 59 | 60 | 61 |
62 | ) 63 | } -------------------------------------------------------------------------------- /disco-db-demo/components/layout.js: -------------------------------------------------------------------------------- 1 | import Navbar from './Navbar'; 2 | import Sidebar from './Sidebar'; 3 | import Box from '@mui/material/Box'; 4 | import CssBaseline from '@mui/material/CssBaseline'; 5 | import Toolbar from '@mui/material/Toolbar'; 6 | import React, { useEffect, useState } from 'react'; 7 | 8 | export default function Layout({ children }) { 9 | const [isLoading, setLoading] = useState(true); 10 | const userNoteArr = []; 11 | const [online, setOnline] = React.useState(true); 12 | const [noteArray, setNewNote] = useState([]); 13 | const [refresh, setRefresh] = useState(false); 14 | //passing online prop to all children 15 | const childrenWithProps = React.Children.map(children, child => { 16 | // Checking isValidElement is the safe way and avoids a typescript error too. 17 | if (React.isValidElement(child)) { 18 | return React.cloneElement(child, { online: online, data: noteArray, setNewNote: setNewNote, setRefresh: setRefresh }); 19 | } 20 | return child; 21 | }); 22 | 23 | 24 | 25 | useEffect(() => { 26 | 27 | const testURL = '/api/hello'; 28 | const devURL = '/user/load'; 29 | fetch(devURL) 30 | .then((res) => res.json()) 31 | .then( (data) => { 32 | //Iterate thru retrived data and create a copy of each object into state array. 33 | data.data.forEach((ele) => { 34 | userNoteArr.push(ele); 35 | }); 36 | setNewNote(userNoteArr); 37 | 38 | setLoading(false); 39 | setRefresh(false); 40 | 41 | }) 42 | .catch((err) => console.log('Error in fetching data', err)) 43 | }, [refresh, online]); 44 | 45 | if (isLoading) return null; 46 | else { 47 | return ( 48 | <> 49 | 50 | 51 | 52 | 53 | 54 | 55 | {childrenWithProps} 56 | 57 | 58 | 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /disco-db-demo/components/weatherAPI.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useState, useEffect } from 'react'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | import WeatherDisplay from './WeatherDisplay'; 5 | 6 | export default function WeatherAPI(props) { 7 | const [weatherReport, setWeatherReport] = useState(null); 8 | const [isLoading, setLoading] = useState(true); 9 | const [isError, setError] = useState(null); 10 | 11 | useEffect(() => { 12 | const devURL = '/api/weather'; 13 | 14 | //sends POST request to api/weather in backend with body as city: 15 | fetch(devURL, { 16 | method: 'POST', 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | body: JSON.stringify( {city: props.city}), 21 | }) 22 | .then( response => response.json()) 23 | .then( data => { 24 | //check for statuscode - this is the object that we will get back from the backend 25 | if (data.statusCode !== 200) { 26 | setLoading(true); 27 | } else { 28 | setWeatherReport(data.data); 29 | setLoading(false); 30 | } 31 | }) 32 | .catch( error => { 33 | console.log('Error: ', error); 34 | setError(error); 35 | setLoading(true); 36 | }) 37 | 38 | }, [props.city]) 39 | 40 | if(isLoading) { 41 | if(props.city) { 42 | return ( 43 |
44 | 45 |
46 | ) 47 | } 48 | else return null; 49 | } else { 50 | return( 51 | 52 | ) 53 | } 54 | } -------------------------------------------------------------------------------- /disco-db-demo/jest-setup.js: -------------------------------------------------------------------------------- 1 | import regeneratorRuntime from 'regenerator-runtime'; 2 | 3 | module.exports = () => { 4 | global.testServer = require('./server/server'); 5 | }; 6 | -------------------------------------------------------------------------------- /disco-db-demo/jest-teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = async (globalConfig) => { 2 | testServer.close(); 3 | }; 4 | -------------------------------------------------------------------------------- /disco-db-demo/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: [ 3 | '**/*.{js,jsx,ts,tsx}', 4 | '!**/*.d.ts', 5 | '!**/node_modules/**', 6 | ], 7 | moduleNameMapper: { 8 | // Handle CSS imports (with CSS modules) 9 | // https://jestjs.io/docs/webpack#mocking-css-modules 10 | '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', 11 | 12 | // Handle CSS imports (without CSS modules) 13 | '^.+\\.(css|sass|scss)$': '/__mocks__/styleMock.js', 14 | 15 | // Handle image imports 16 | // https://jestjs.io/docs/webpack#handling-static-assets 17 | '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `/__mocks__/fileMock.js`, 18 | 19 | // Handle module aliases 20 | '^@/components/(.*)$': '/components/$1', 21 | }, 22 | // Add more setup options before each test is run 23 | // setupFilesAfterEnv: ['/jest.setup.js'], 24 | testPathIgnorePatterns: ['/node_modules/', '/.next/'], 25 | testEnvironment: 'jsdom', 26 | transform: { 27 | // Use babel-jest to transpile tests with the next/babel preset 28 | // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object 29 | '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }], 30 | }, 31 | transformIgnorePatterns: [ 32 | '/node_modules/', 33 | '^.+\\.module\\.(css|sass|scss)$', 34 | ], 35 | } -------------------------------------------------------------------------------- /disco-db-demo/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /disco-db-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disco-db-demo", 3 | "private": true, 4 | "scripts": { 5 | "dev": "nodemon server/server.js", 6 | "next-dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest --watch", 11 | "test:e2e": "playwright test" 12 | }, 13 | "dependencies": { 14 | "@emotion/react": "^11.8.1", 15 | "@emotion/styled": "^11.8.1", 16 | "@fontsource/roboto": "^4.5.3", 17 | "@mui/icons-material": "^5.4.2", 18 | "@mui/material": "^5.4.2", 19 | "axios": "^0.26.0", 20 | "bcrypt": "^5.0.1", 21 | "cookie-parser": "^1.4.6", 22 | "dotenv": "^16.0.0", 23 | "express": "^4.17.2", 24 | "mongoose": "^6.2.2", 25 | "next": "12.0.10", 26 | "react": "17.0.2", 27 | "react-dom": "17.0.2", 28 | "regenerator-runtime": "^0.13.9" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.17.4", 32 | "@babel/preset-env": "^7.16.11", 33 | "@playwright/test": "^1.19.1", 34 | "@testing-library/jest-dom": "^5.16.2", 35 | "@testing-library/react": "^12.1.3", 36 | "babel-jest": "^27.5.1", 37 | "cross-fetch": "^3.1.5", 38 | "eslint": "8.9.0", 39 | "eslint-config-next": "12.0.10", 40 | "identity-obj-proxy": "^3.0.0", 41 | "jest": "^27.5.1", 42 | "nodemon": "^2.0.15", 43 | "service-worker-mock": "^2.0.5", 44 | "supertest": "^6.2.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /disco-db-demo/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import '@fontsource/roboto/300.css'; 3 | import '@fontsource/roboto/400.css'; 4 | import '@fontsource/roboto/500.css'; 5 | import '@fontsource/roboto/700.css'; 6 | import { useEffect } from 'react'; 7 | 8 | function MyApp({ Component, pageProps }) { 9 | // // // service worker 10 | useEffect(() => { 11 | if("serviceWorker" in navigator && 'SyncManager' in window) { 12 | window.addEventListener("load", function () { 13 | navigator.serviceWorker.register("../swCacheSite-indexedDB.js", {type: 'module', scope: '/'}).then( 14 | function (registration) { 15 | console.log("Service Worker registration successful with scope: ", registration.scope); 16 | }, 17 | function (err) { 18 | console.log("Service Worker registration failed: ", err); 19 | } 20 | ); 21 | // navigator.serviceWorker.ready.then(function(swRegistration) { 22 | // console.log('successfully requested a one time sync') 23 | // return swRegistration.sync.register('myFirstSync'); 24 | // }); 25 | }); 26 | } 27 | }, []) 28 | 29 | 30 | 31 | const getLayout = Component.getLayout || ((page) => page) 32 | return getLayout() 33 | } 34 | 35 | export default MyApp 36 | -------------------------------------------------------------------------------- /disco-db-demo/pages/about.js: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return ( 3 |
4 |

About Page

5 |

Hello

6 |
7 | ) 8 | } -------------------------------------------------------------------------------- /disco-db-demo/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | //const body = req.body; 5 | //const body = {actionSuccess: true}; 6 | const body = [{_id: 'foobar', title: 'foo', content: 'bar'}, {_id: '123', title: 'foobar', content: 'I love pizza'}]; 7 | res.status(200).json({ body }) 8 | } 9 | -------------------------------------------------------------------------------- /disco-db-demo/pages/auth/login.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Avatar from '@mui/material/Avatar'; 3 | import Button from '@mui/material/Button'; 4 | import CssBaseline from '@mui/material/CssBaseline'; 5 | import TextField from '@mui/material/TextField'; 6 | import FormControlLabel from '@mui/material/FormControlLabel'; 7 | import Checkbox from '@mui/material/Checkbox'; 8 | import { default as MUILink } from '@mui/material/Link'; 9 | import Link from 'next/link'; 10 | import Grid from '@mui/material/Grid'; 11 | import Box from '@mui/material/Box'; 12 | import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; 13 | import Typography from '@mui/material/Typography'; 14 | import Container from '@mui/material/Container'; 15 | import { createTheme, ThemeProvider } from '@mui/material/styles'; 16 | import { useRouter } from 'next/router'; 17 | import Alert from '@mui/material/Alert'; 18 | 19 | function Copyright(props) { 20 | return ( 21 | 22 | {'Copyright © '} 23 | 24 | DiscoDB 25 | {' '} 26 | {new Date().getFullYear()} 27 | {'.'} 28 | 29 | ); 30 | } 31 | 32 | const theme = createTheme(); 33 | 34 | export default function SignIn() { 35 | const router = useRouter(); 36 | const [auth, setAuth] = React.useState(null); 37 | 38 | const handleSubmit = (event) => { 39 | event.preventDefault(); 40 | const data = new FormData(event.currentTarget); 41 | // eslint-disable-next-line no-console 42 | 43 | const loginBody = { 44 | username: data.get('username'), 45 | password: data.get('password'), 46 | } 47 | 48 | const testURL = '/api/hello'; 49 | const devURL = '/auth/login'; 50 | fetch(devURL, { 51 | method: 'POST', 52 | headers: { 53 | 'Content-Type': 'application/json', 54 | }, 55 | body: JSON.stringify(loginBody), 56 | }) 57 | .then(res => res.json()) 58 | .then(data => { 59 | console.log('Success', data); 60 | //get back response from backend to verify user authentication 61 | //if success, redirect user to user endpoint 62 | if (!data.actionSuccess) { 63 | setAuth(false); 64 | } else { 65 | setAuth(true); 66 | //temp username to add to localStorage 67 | localStorage.setItem('user', loginBody.username); 68 | router.push('/user'); 69 | } 70 | 71 | }) 72 | .catch(err => console.log('Error', err)) 73 | }; 74 | 75 | return ( 76 | 77 | 78 | 79 | 87 | 88 | 89 | 90 | 91 | Sign in 92 | 93 | 94 | 104 | 114 | {auth === false && 115 | 116 | Please enter a valid username and password 117 | 118 | } 119 | } 121 | label="Remember me" 122 | /> 123 | 131 | 132 | 133 | 134 | 135 | Forgot password? 136 | 137 | 138 | 139 | 140 | 141 | 142 | {"Don't have an account? Sign Up"} 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | ); 153 | } 154 | -------------------------------------------------------------------------------- /disco-db-demo/pages/auth/signup.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Avatar from '@mui/material/Avatar'; 3 | import Button from '@mui/material/Button'; 4 | import CssBaseline from '@mui/material/CssBaseline'; 5 | import TextField from '@mui/material/TextField'; 6 | import FormControlLabel from '@mui/material/FormControlLabel'; 7 | import Checkbox from '@mui/material/Checkbox'; 8 | import {default as MUILink} from '@mui/material/Link'; 9 | import Link from 'next/link'; 10 | import Grid from '@mui/material/Grid'; 11 | import Box from '@mui/material/Box'; 12 | import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; 13 | import Typography from '@mui/material/Typography'; 14 | import Container from '@mui/material/Container'; 15 | import { createTheme, ThemeProvider } from '@mui/material/styles'; 16 | import { useRouter } from 'next/router'; 17 | import Alert from '@mui/material/Alert'; 18 | 19 | function Copyright(props) { 20 | return ( 21 | 22 | {'Copyright © '} 23 | 24 | DiscoDB 25 | {' '} 26 | {new Date().getFullYear()} 27 | {'.'} 28 | 29 | ); 30 | } 31 | 32 | const theme = createTheme(); 33 | 34 | export default function SignUp() { 35 | const [auth, setAuth] = React.useState(null); 36 | const router = useRouter(); 37 | 38 | const handleSubmit = (event) => { 39 | event.preventDefault(); 40 | const data = new FormData(event.currentTarget); 41 | 42 | const signupBody = { 43 | username: data.get('username'), 44 | password: data.get('password'), 45 | firstName: data.get('firstName'), 46 | email: data.get('email') 47 | } 48 | 49 | const testURL = '/api/hello'; 50 | const devURL = '/auth/signup'; 51 | fetch(devURL, { 52 | method: 'POST', 53 | headers: { 54 | 'Content-Type': 'application/json', 55 | }, 56 | body: JSON.stringify(signupBody), 57 | }) 58 | .then(res => res.json()) 59 | .then(data => { 60 | console.log('Success', data); 61 | //have backend send back response that user successfully created account 62 | //if success, redirect user to login page, else have them try again 63 | if (!data.actionSuccess) { 64 | setAuth(false); 65 | } else { 66 | setAuth(true); 67 | //temp username to add to localStorage 68 | localStorage.setItem('user', signupBody.username); 69 | router.push('/user'); 70 | } 71 | }) 72 | .catch(err => console.log('Error', err)) 73 | }; 74 | 75 | return ( 76 | 77 | 78 | 79 | 87 | 88 | 89 | 90 | 91 | Sign up 92 | 93 | 94 | 95 | 96 | 104 | 105 | 106 | 113 | 114 | 115 | 123 | 124 | 125 | 134 | 135 | 136 | {auth === false && 137 | 138 | Username already exists. Please try again 139 | 140 | } 141 | 149 | 150 | 151 | 152 | 153 | Already have an account? Sign in 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /disco-db-demo/pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import styles from '../styles/Home.module.css' 4 | import Link from 'next/link' 5 | 6 | export default function Home() { 7 | return ( 8 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /disco-db-demo/pages/user/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import Typography from '@mui/material/Typography'; 4 | import Weather from '../../components/Weather'; 5 | import Layout from '../../components/layout'; 6 | 7 | export default function User(props) { 8 | console.log(props); 9 | 10 | return ( 11 | 12 | 13 | Hello User 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | User.getLayout = function getLayout(user) { 21 | return ( 22 | 23 | {user} 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /disco-db-demo/pages/user/notes.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import Typography from '@mui/material/Typography'; 4 | import Layout from '../../components/layout'; 5 | import NotesContainer from '../../components/Notes'; 6 | import { useRouter } from 'next/router'; 7 | 8 | export default function Notes(props) { 9 | //grab query paramaters from note 10 | const [setContent, setNewContent] = React.useState(''); 11 | const [setTitle, setNewTitle] = React.useState(''); 12 | 13 | const router = useRouter(); 14 | let noteID = Object.keys(router.query)[0]; 15 | 16 | React.useEffect(() => { 17 | for (let i = 0; i < props.data.length; i++) { 18 | if (props.data[i]._id === noteID) { 19 | setNewTitle(props.data[i].title); 20 | setNewContent(props.data[i].content); 21 | } 22 | } 23 | // setNewContent(noteContent) 24 | // setNewTitle(noteTitle) 25 | }, [props.data, noteID]); 26 | 27 | return ( 28 | 29 | 30 | User Notes 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | 38 | Notes.getLayout = function getLayout(notes) { 39 | return ( 40 | 41 | {notes} 42 | 43 | ) 44 | } -------------------------------------------------------------------------------- /disco-db-demo/public/backgroundSync.js: -------------------------------------------------------------------------------- 1 | import { idbPromise, dbGlobals } from './discoGlobals.js'; 2 | 3 | /** 4 | * @property {Function} accessObjectStore Access object store in IDB database and start a transaction 5 | * @param {String} storeName Object store to be accessed for transaction 6 | * @param {String} method Method for transaction, "readwrite, readonly" 7 | * @return {Object} Accessed Object store Object 8 | */ 9 | function accessObjectStore (storeName, method) { 10 | return idbPromise.DB.transaction([storeName], method).objectStore(storeName) 11 | }; 12 | /** 13 | * @property {Function} discoAddToQueue Adds Object into Object store 14 | * @param {Object} dataObject Objected to be added to Object store 15 | * 16 | */ 17 | function discoAddToQueue (dataObject) { 18 | //Open a transaction to object store 'Queue' 19 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite') 20 | //Add data to object store 21 | store.add(dataObject) 22 | }; 23 | /** 24 | * @property {Function} discoRegisterSync Request a "Sync" event to reattempt request when network is online. 25 | * 26 | */ 27 | async function discoRegisterSync() { 28 | try { 29 | const register = await registration.sync.register('failed_requests'); 30 | return register; 31 | } catch(error) { 32 | console.log('Error:' , error); 33 | return error; 34 | } 35 | }; 36 | 37 | /** 38 | * @property {Function} discoSyncToServer Accesses the Queue Object Store and re-sends all requests saved in application/json. 39 | * 40 | */ 41 | function discoSyncToServer() { 42 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite'); 43 | const request = store.getAll(); 44 | 45 | request.onsuccess = function (event) { 46 | const httpQueue = event.target.result; 47 | //Comes back as an array of objects 48 | //Iterate Queue store and initialize Fetch request 49 | httpQueue.forEach((data) => { 50 | const { url, method, body } = data 51 | const headers = {'Content-Type': 'application/json'}; 52 | fetch(url, { 53 | method: method, 54 | headers: headers, 55 | body: JSON.stringify(body) 56 | }) 57 | .then((res) => res.json()) 58 | .then((res) => { 59 | //Previous transaction was closed due to getAll() 60 | //Reopen object store and delete the corresponding object on successful HTTP request 61 | const newStore = accessObjectStore(dbGlobals.syncQueue, 'readwrite'); 62 | newStore.delete(data.id); 63 | }) 64 | .catch((error) => { 65 | console.error('Failed to sync data to server:', error); 66 | throw error 67 | }) 68 | }); 69 | } 70 | request.onerror = (err) => { 71 | console.log('Attempt to sync queue failed:', err); 72 | } 73 | }; 74 | 75 | export { discoAddToQueue, discoRegisterSync, discoSyncToServer }; -------------------------------------------------------------------------------- /disco-db-demo/public/configMap.js: -------------------------------------------------------------------------------- 1 | // const fs = require('fs'); 2 | // const path = require('path'); 3 | // import * as fs from 'fs'; 4 | // import path from 'path'; 5 | 6 | import { dbGlobals } from './discodb.config.js' 7 | 8 | // function find(targetPath) { 9 | // return findStartingWith(path.dirname(require.main.filename), targetPath); 10 | // } 11 | 12 | // function findStartingWith(start, target) { 13 | // const file = path.join(start, target); 14 | // try { 15 | // data = fs.readFileSync(file, 'utf-8'); 16 | // return JSON.parse(data); 17 | // } catch (err) { 18 | 19 | // if (path.dirname(start) !== start) { 20 | // return findStartingWith(path.dirname(start), target); 21 | // } 22 | // } 23 | // } 24 | 25 | // const dbGlobals = JSON.parse(userConfig); 26 | console.log(dbGlobals) 27 | 28 | // const dbGlobals = find('discodb.config.json'); 29 | // module.exports = dbGlobals; 30 | export default dbGlobals; -------------------------------------------------------------------------------- /disco-db-demo/public/discoGlobals.js: -------------------------------------------------------------------------------- 1 | // const configVariables = require('./configMap.js'); 2 | // import dbGlobals from './configMap.js'; 3 | import { dbGlobals } from './discodb.config.js' 4 | const idbPromise = { 5 | DB: null 6 | } 7 | 8 | const onlineUrlArr = []; 9 | dbGlobals.onlineRoutes.forEach(el => { 10 | onlineUrlArr.push(el.url) 11 | }); 12 | 13 | const offlineUrlArr = []; 14 | dbGlobals.offlineRoutes.forEach(el => { 15 | offlineUrlArr.push(el.url); 16 | }); 17 | 18 | // module.exports = { idbPromise, onlineUrlStoreMap, offlineUrlStoreMap }; 19 | 20 | // module.exports = { 21 | // DB: null, 22 | 23 | // } 24 | 25 | export { dbGlobals, idbPromise, onlineUrlArr, offlineUrlArr } -------------------------------------------------------------------------------- /disco-db-demo/public/discoSync.js: -------------------------------------------------------------------------------- 1 | import { discoConnect, discoGetAll, discoDeleteOne, discoUpdateOne, discoAdd, discoDeleteAll } from './idbOperations.js'; 2 | import { discoAddToQueue, discoRegisterSync } from './backgroundSync.js'; 3 | import { idbPromise , dbGlobals } from './discoGlobals.js'; 4 | 5 | /** 6 | * @property {Function} discoSyncOffline Executes different IndexedDB logic based on the value of passed in method 7 | * @param {String} method This is the method property of the intercepted fetch request 8 | * @param {String} url This is the url property of the intercepted fetch request 9 | * @param {String} store This is the store property associated with the url provided in the config file 10 | * @param {Request} eventRequest This is the cloned version of the intercepted fetch request 11 | * 12 | */ 13 | function discoSyncOffline(method, url, clonedRequest) { 14 | // assuming store can be managed by config file and imported into indexedDB.js 15 | // config.store = store 16 | switch(method) { 17 | case 'GET': 18 | if (idbPromise.DB) { 19 | // if store 20 | return discoGetAll().then((data) => { 21 | //REVISIT THIS, make sure to change data back to data!! 22 | const responseBody = { data }; 23 | const IDBData = new Response(JSON.stringify(responseBody)); 24 | return IDBData; 25 | }) 26 | } else { 27 | return discoConnect( () => { 28 | discoGetAll().then((data) => { 29 | const responseBody = {data: data}; 30 | const IDBData = new Response(JSON.stringify(responseBody)); 31 | return IDBData; 32 | }); 33 | }) 34 | } 35 | case 'DELETE': 36 | return clonedRequest.json() 37 | .then((data) => { 38 | const reqBody = { 39 | url: url, 40 | method: method, 41 | body: data 42 | }; 43 | discoRegisterSync(); 44 | discoAddToQueue(reqBody); 45 | const keypath = dbGlobals.keypath; 46 | const id = data[keypath]; 47 | //call function to DELETE note 48 | discoDeleteOne(id); 49 | const deleteResponse = new Response(JSON.stringify({})); 50 | return deleteResponse; 51 | }) 52 | .catch( err => { 53 | console.log('this is in the dbDeleteOne catch block: ', err); 54 | }) 55 | case 'PATCH': 56 | return clonedRequest.json() 57 | .then((data) => { 58 | const reqBody = { 59 | url: url, 60 | method: method, 61 | body: data 62 | }; 63 | discoRegisterSync(); 64 | discoAddToQueue(reqBody); 65 | //call function to UPDATE note 66 | const keypath = dbGlobals.keypath; 67 | const id = data[keypath]; 68 | discoUpdateOne(data); 69 | 70 | // returns empty object to trigger rerender in our app 71 | // assumes developer does not want to do anything with the response 72 | const patchResponse = new Response(JSON.stringify({})); 73 | return patchResponse; 74 | }) 75 | default: 76 | console.log('this url is not configured'); 77 | return caches.match(clonedRequest) 78 | .then(response => { 79 | return response 80 | }) 81 | } 82 | } 83 | 84 | /** 85 | * @property {Function} discoSyncOnline Establishes connection to indexedDB & create Object Stores as specified in Configuration. 86 | * @param {String} method This is the method property of the intercepted fetch request 87 | * @param {String} url This is the url property of the intercepted fetch request 88 | * @param {String} store This is the store property associated with the url provided in the config file 89 | * @param {Request} clonedResponse This is the cloned version of the intercepted fetch response 90 | * 91 | */ 92 | function discoSyncOnline(method, url, clonedResponse) { 93 | switch(method) { 94 | case 'GET': 95 | const resCloneDB = clonedResponse; 96 | resCloneDB.json().then(data => { 97 | //delete existing indexedDB data 98 | if (idbPromise.DB) { 99 | discoDeleteAll(); 100 | } else { 101 | discoConnect( () => { 102 | discoDeleteAll(); 103 | }) 104 | } 105 | //populate indexedDB here 106 | data.data.forEach( note => { 107 | if (idbPromise.DB) { 108 | discoAdd(note); 109 | } else { 110 | discoConnect( () => { 111 | discoAdd(note); 112 | }) 113 | } 114 | }) 115 | }); 116 | break; 117 | default: 118 | console.log('this method is not configured'); 119 | break; 120 | } 121 | } 122 | 123 | 124 | export { discoSyncOffline, discoSyncOnline }; -------------------------------------------------------------------------------- /disco-db-demo/public/discodb.config.js: -------------------------------------------------------------------------------- 1 | const dbGlobals = 2 | { 3 | "version": 9, 4 | "databaseName": "notesDB", 5 | "storeName": "notesStore", 6 | "syncQueue": "Queue", 7 | "keypath": "_id", 8 | "onlineRoutes": [ 9 | { 10 | "url": "http://localhost:3000/user/load", 11 | } 12 | ], 13 | "offlineRoutes": [ 14 | { 15 | "url": "http://localhost:3000/user/load", 16 | }, 17 | { 18 | "url": "http://localhost:3000/user/notes", 19 | } 20 | ] 21 | } 22 | 23 | 24 | export { dbGlobals }; -------------------------------------------------------------------------------- /disco-db-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/DiscoDB/b8553c013c84aaf13f2aa7226446770921386d3c/disco-db-demo/public/favicon.ico -------------------------------------------------------------------------------- /disco-db-demo/public/idbOperations.js: -------------------------------------------------------------------------------- 1 | import { idbPromise, dbGlobals } from './discoGlobals.js'; 2 | 3 | /** 4 | * @property {Function} discoConnect Establishes connection to indexedDB & create Object Stores as specified in Configuration. 5 | * @param {Function} callback 6 | * 7 | */ 8 | function discoConnect(callback) { 9 | return new Promise((resolve, reject) => { 10 | let req = indexedDB.open(dbGlobals.databaseName, dbGlobals.version) 11 | req.onerror = (err) => { 12 | //could not open db 13 | console.log('Error: ', err); 14 | idbPromise.DB = null; 15 | reject(err); 16 | }; 17 | req.onupgradeneeded = (event) => { 18 | let db = event.target.result; 19 | // Database Version Upgraded 20 | if (!db.objectStoreNames.contains(dbGlobals.storeName)) { 21 | db.createObjectStore(dbGlobals.storeName, { 22 | keyPath: dbGlobals.keypath, 23 | }); 24 | } 25 | if (!db.objectStoreNames.contains(dbGlobals.syncQueue)) { 26 | //Creating Object Store for Sync Queue 27 | db.createObjectStore(dbGlobals.syncQueue, { 28 | keyPath: 'id', autoIncrement: true, 29 | }) 30 | } 31 | }; 32 | req.onsuccess = (event) => { 33 | idbPromise.DB = event.target.result; 34 | //Database connected 35 | if (callback) { 36 | callback(); 37 | } 38 | resolve(idbPromise.DB); 39 | }; 40 | }) 41 | }; 42 | 43 | /** 44 | * @property {Function} discoAdd Adds an object in to the configured Object Store. 45 | * @param {Object} dataObject Object data to be stored. 46 | * 47 | */ 48 | function discoAdd(dataObject) { 49 | // return new Promise ( (resolve, reject) => { 50 | if (dataObject && idbPromise.DB) { 51 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 52 | 53 | tx.onerror = (err) => { 54 | console.log('Error:', err); 55 | // reject(err); 56 | }; 57 | tx.oncomplete = (event) => { 58 | //Data added successfully 59 | }; 60 | 61 | let store = tx.objectStore(dbGlobals.storeName); 62 | let req = store.put(dataObject); 63 | 64 | req.onsuccess = (event) => { 65 | // const result = event.target.result; 66 | // resolve(result); 67 | }; 68 | } else { 69 | console.log('No data provided.'); 70 | } 71 | // }) 72 | }; 73 | 74 | /** 75 | * @property {Function} discoDeleteAll Deletes all properties of the configured Object Store. 76 | * 77 | */ 78 | function discoDeleteAll() { 79 | return new Promise( (resolve, reject) => { 80 | if (idbPromise.DB) { 81 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 82 | tx.onerror = (err) => { 83 | console.log('Error:', err); 84 | reject(err); 85 | }; 86 | tx.oncomplete = (event) => { 87 | // data deleted successfully 88 | }; 89 | let store = tx.objectStore(dbGlobals.storeName); 90 | const req = store.clear(); 91 | req.onsuccess = (event) => { 92 | const result = event.target.result; 93 | resolve(result); 94 | }; 95 | } else { 96 | console.log('DB is not connected'); 97 | } 98 | }) 99 | }; 100 | 101 | /** 102 | * @property {Function} discoGetAll Gets all properties saved on the configured Object Store. 103 | * @return {Array} array of objects containing all properties in the Object Store. 104 | */ 105 | function discoGetAll() { 106 | return new Promise((resolve, reject) => { 107 | if (idbPromise.DB) { 108 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readonly'); 109 | tx.onerror = (err) => { 110 | console.log('Error: ', err); 111 | reject(err); 112 | }; 113 | tx.oncomplete = (event) => { 114 | //Transaction successful, all objects retrieved. 115 | }; 116 | let store = tx.objectStore(dbGlobals.storeName); 117 | const req = store.getAll(); 118 | req.onsuccess = (event) => { 119 | const result = event.target.result; 120 | resolve(result); 121 | }; 122 | } else { 123 | console.log('DB is not connected'); 124 | } 125 | }) 126 | } 127 | 128 | /** 129 | * @property {Function} discoDeleteOne Deletes one property on the configured Object Store. 130 | * @param {String} id The id of the object on the configured Object Store * 131 | */ 132 | function discoDeleteOne(id) { 133 | return new Promise( (resolve, reject) => { 134 | if (idbPromise.DB) { 135 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 136 | tx.onerror = (err) => { 137 | console.log('Error: ', err); 138 | reject(err); 139 | }; 140 | tx.oncomplete = (event) => { 141 | //Transaction successful 142 | }; 143 | let store = tx.objectStore(dbGlobals.storeName); 144 | const req = store.delete(id); 145 | req.onsuccess = (event) => { 146 | const result = event.target.result; 147 | resolve(result); 148 | }; 149 | } else { 150 | console.log('DB is not connected'); 151 | } 152 | }) 153 | } 154 | 155 | /** 156 | * @property {Function} discoUpdateOne Updates one property on the configured Object Store. 157 | * @param {Object} dataObject The req body object on the configured Object Store 158 | * 159 | */ 160 | function discoUpdateOne(dataObject) { 161 | return new Promise ( (resolve, reject) => { 162 | if (idbPromise.DB) { 163 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 164 | tx.onerror = (err) => { 165 | console.log('Error: ', err); 166 | reject(err); 167 | }; 168 | tx.oncomplete = (event) => { 169 | //Transaction successful 170 | }; 171 | let store = tx.objectStore(dbGlobals.storeName); 172 | const req = store.put(dataObject); 173 | req.onsuccess = (event) => { 174 | const result = event.target.result; 175 | resolve(result); 176 | }; 177 | } else { 178 | console.log('DB is not connected'); 179 | } 180 | }) 181 | }; 182 | 183 | export { discoConnect, discoAdd, discoDeleteAll, discoGetAll, discoDeleteOne, discoUpdateOne }; -------------------------------------------------------------------------------- /disco-db-demo/public/swCacheSite-indexedDB.js: -------------------------------------------------------------------------------- 1 | import { discoConnect } from './idbOperations.js'; 2 | import { discoSyncToServer } from './backgroundSync.js'; 3 | import { discoSyncOffline, discoSyncOnline } from './discoSync.js'; 4 | import { onlineUrlArr, offlineUrlArr, dbGlobals, idbPromise } from './discoGlobals.js'; 5 | 6 | 7 | const cacheName = 'my-site-cache-v3'; 8 | 9 | self.addEventListener('install', event => { 10 | console.log('Attempting to install service worker and cache static assets'); 11 | self.skipWaiting(); 12 | console.log('opening DB since sw is activated') 13 | discoConnect(); 14 | }); 15 | 16 | self.addEventListener('activate', event => { 17 | console.log('Activating new service worker...'); 18 | const cacheAllowlist = [cacheName]; 19 | //Remove unwanted caches 20 | event.waitUntil( 21 | caches.keys().then(cacheNames => { 22 | return Promise.all( 23 | cacheNames.map(cacheName => { 24 | if (cacheAllowlist.indexOf(cacheName) === -1) { 25 | return caches.delete(cacheName); 26 | } 27 | }) 28 | ) 29 | }) 30 | ); 31 | //Force SW to become available to all pages 32 | event.waitUntil(self.clients.claim()); 33 | }); 34 | 35 | self.addEventListener('fetch', event => { 36 | //clone the request so that the body of the request will still be available 37 | const reqClone = event.request.clone(); 38 | const { url, method } = event.request; 39 | event.respondWith( 40 | fetch(event.request) 41 | .then( (response) => { 42 | // Make clone of response 43 | const resCloneCache = response.clone(); 44 | const resCloneDB = response.clone() 45 | //Open cache 46 | caches 47 | .open(cacheName) 48 | .then(cache => { 49 | //Add response to cache 50 | cache.put(event.request, resCloneCache); 51 | }) 52 | //invoke online reducer to populate indexedDB 53 | if (onlineUrlArr.includes(url)){ 54 | discoSyncOnline(method, url, resCloneDB); 55 | } 56 | return response; 57 | }) 58 | // if network is unavailable 59 | .catch((err) => { 60 | //invoke offline reducer to perform RUD functions to indexedDB 61 | if (offlineUrlArr.includes(url)){ 62 | return discoSyncOffline(method, url, reqClone); 63 | } 64 | return caches.match(reqClone) 65 | .then(response => { 66 | return response 67 | }) 68 | }) 69 | ) 70 | }); 71 | 72 | //When back online, listener will be invoked. 73 | //WaitUntil: waits for service workers until promise resolves 74 | //Then invoke syncData 75 | self.addEventListener('sync', (event) => { 76 | if(event.tag === 'failed_requests'){ 77 | event.waitUntil(discoSyncToServer()) 78 | }; 79 | }); 80 | -------------------------------------------------------------------------------- /disco-db-demo/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /disco-db-demo/server/controllers/authController.js: -------------------------------------------------------------------------------- 1 | // const mongoose = require('mongoose'); 2 | const bcrypt = require('bcrypt'); 3 | //const responseModel = require('../models/responseModel'); 4 | const { createResponse } = require('../models/responseModel'); 5 | // require User object from db schema 6 | const { Users } = require('../models/model'); 7 | 8 | // function to encrypt account password 9 | function encrypt(password) { 10 | const workFactor = 10; 11 | return bcrypt.hash(password, workFactor); 12 | } 13 | 14 | // auth middleware functions 15 | const authController = { 16 | 17 | signup(req, res, next) { 18 | console.log('this is in the authcontroller/signup: ', req.body) 19 | // encrypt password 20 | encrypt(req.body.password) 21 | .then(hash => { 22 | Users.create({ 23 | username: req.body.username, 24 | password: hash, 25 | first_name: req.body.firstName, // double check firstName on req.body 26 | email: req.body.email 27 | }, 28 | (err, result) => { 29 | if (err) { 30 | return next(err); 31 | } 32 | return next(); 33 | }) 34 | }) 35 | }, 36 | 37 | 38 | login(req, res, next) { 39 | console.log('this is in the authcontroller/login', req.body); 40 | const { username, password } = req.body; 41 | 42 | Users.findOne({ username: username }, 43 | (err, result) => { 44 | 45 | //if user not found, there is no error but result is set to null 46 | //causing error when you try to destructure password from result 47 | if (result === null) { 48 | return res.status(406).json(createResponse(false, 406, 'Wrong username and/or password')); 49 | } 50 | if(err) { 51 | return next(err); 52 | } 53 | // destructure hashedPassword and compare to user's inputed password 54 | const { password: hashedPassword } = result; 55 | bcrypt.compare(password, hashedPassword, (err, bcryptRes) => { 56 | if (bcryptRes) { 57 | console.log('passwords match!'); 58 | return next(); 59 | } else { 60 | console.log('passwords do not match'); 61 | return res.status(406).json(createResponse(false, 406, 'Wrong username and/or password')); 62 | } 63 | }) 64 | }); 65 | }, 66 | 67 | generateCookie(req, res, next) { 68 | 69 | const { username } = req.body; 70 | res.cookie('username', username, { httpOnly: true }); 71 | return next(); 72 | }, 73 | 74 | logout(req, res, next) { 75 | res.clearCookie('username'); 76 | return next(); 77 | } 78 | }; 79 | 80 | 81 | 82 | 83 | // logout 84 | 85 | module.exports = authController; -------------------------------------------------------------------------------- /disco-db-demo/server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const { Notes } = require('../models/model'); 2 | const userController = {}; 3 | 4 | // get all user notes from Notes collection 5 | userController.getUserNotes = (req, res, next) => { 6 | const {username} = req.cookies; 7 | // const {username} = req.body; 8 | console.log('Retrieving all notes from username: ', username) 9 | Notes.find({username: username}) 10 | .then(data => { 11 | console.log(data); 12 | res.locals.data = data; 13 | return next(); 14 | }) 15 | .catch(err => { 16 | return next(err); 17 | }) 18 | } 19 | 20 | // create a new notes entry in Notes collection 21 | userController.createAndGetNewNote = (req, res, next) => { 22 | const {username, createdAt} = req.body; 23 | console.log('Creating a new note for username: ', username) 24 | Notes.create({username: [username], title: '', content: '', updatedAt: createdAt, createdAt: createdAt}) 25 | .then(data => { 26 | console.log(data); 27 | res.locals.data = data; 28 | return next(); 29 | }) 30 | .catch(err => { 31 | return next(err); 32 | }) 33 | } 34 | 35 | // modifies an entry in Notes collection 36 | userController.modifyAndGetNote = (req, res, next) => { 37 | const {_id, title, content, updatedAt} = req.body; 38 | console.log('Modifying note _id: ', _id) 39 | Notes.findByIdAndUpdate(_id, {title: title, content: content, updatedAt: updatedAt}, {new: true}) 40 | .then(data => { 41 | console.log(data); 42 | res.locals.data = data; 43 | return next(); 44 | }) 45 | .catch(err => { 46 | return next(err); 47 | }) 48 | } 49 | 50 | // deletes an entry in Notes collection 51 | userController.deleteNote = (req, res, next) => { 52 | const {_id, username} = req.body; 53 | console.log('Deleting note _id: ', _id) 54 | Notes.deleteOne({_id: _id}) 55 | .then(data => { 56 | console.log('Note successfully deleted') 57 | return next(); 58 | }) 59 | .catch(err => { 60 | return next(err); 61 | }) 62 | } 63 | 64 | module.exports = userController; -------------------------------------------------------------------------------- /disco-db-demo/server/controllers/weatherController.js: -------------------------------------------------------------------------------- 1 | const weatherController = {}; 2 | const axios = require('axios'); 3 | const { createResponse } = require('../models/responseModel'); 4 | 5 | const API_KEY = '90c77ec3042549f5cc0b7aa750034457' 6 | const UNITS = 'imperial'; 7 | const LANG = 'en'; 8 | 9 | // make request to openweathermap API 10 | weatherController.getWeather = (req, res, next) => { 11 | let CITY = req.body.city; 12 | const weatherURL = `http://api.openweathermap.org/data/2.5/weather?q=${CITY}&lang=${LANG}&appid=${API_KEY}&units=${UNITS}`; 13 | axios({ 14 | method: 'get', 15 | url: weatherURL, 16 | }).then(data => { 17 | console.log(data.data); 18 | res.locals = data.data; 19 | return next(); 20 | }) 21 | .catch(err => { 22 | return res.status(404).json(createResponse(false, 404, 'city not found')); 23 | // return next(err); 24 | }) 25 | } 26 | 27 | module.exports = weatherController; 28 | -------------------------------------------------------------------------------- /disco-db-demo/server/models/dbConnection.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | require('dotenv').config({path: path.resolve(__dirname, '../../.env')}); 3 | const mongoose = require('mongoose'); 4 | const URI = process.env.DB_CONNECTION_STRING; 5 | 6 | 7 | // unsure if the query method is what we call to query the DB 8 | // eric - changed to connection 9 | // connection method invoked in server.js to establish connection, possibly not needed 10 | module.exports = { 11 | connection: function (){ 12 | mongoose.connect(URI, { useNewUrlParser: true, useUnifiedTopology: true, dbName: 'demo' }); 13 | mongoose.connection.once('open', ()=>{ 14 | console.log('Connected to MongoDB'); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /disco-db-demo/server/models/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema({ 6 | username: {type: String, required: true, unique: true}, 7 | password: {type: String, required: true}, 8 | first_name: {type: String}, 9 | email: {type: String} 10 | }); 11 | 12 | const noteSchema = new Schema({ 13 | username: {type: [String], required: true}, 14 | title: {type: String}, 15 | content: {type: String}, 16 | 17 | // unsure if this date field is correctly written 18 | createdAt: {type: Date}, 19 | updatedAt: {type: Date} 20 | }) 21 | 22 | // creating model objects 23 | const Users = mongoose.model('user', userSchema); 24 | const Notes = mongoose.model('note', noteSchema); 25 | 26 | // exporting model object 27 | module.exports = { Users, Notes } -------------------------------------------------------------------------------- /disco-db-demo/server/models/responseModel.js: -------------------------------------------------------------------------------- 1 | function createResponse(actionSuccess, statusCode = 200, message = '', data = {}) { 2 | return { 3 | actionSuccess, // boolean 4 | statusCode, // HTTP code 5 | message, // string 6 | data // object 7 | }; 8 | } 9 | 10 | 11 | module.exports = { createResponse }; -------------------------------------------------------------------------------- /disco-db-demo/server/routers/apiRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const weatherController = require('../controllers/weatherController') 4 | const { createResponse } = require('../models/responseModel'); 5 | 6 | router.post('/weather', 7 | weatherController.getWeather, 8 | (req, res) => { 9 | return res.status(200).json(createResponse(true, 200, 'API call successful', res.locals)); 10 | }); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /disco-db-demo/server/routers/authRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const authController = require('../controllers/authController') 4 | const { createResponse } = require('../models/responseModel'); 5 | 6 | // Do we need this? Or will it be handled by nextJS? 7 | // router.get('/login', 8 | // (req, res) => { 9 | // res.sendStatus(200); 10 | // }); 11 | 12 | // Authenticate user using bcrypt. Send error if not valid. Send session cookie once validated. 13 | router.post('/login', 14 | authController.login, 15 | authController.generateCookie, 16 | (req, res) => { 17 | return res.status(200).json(createResponse(true, 200, 'Login successful')); 18 | }); 19 | 20 | // Do we need this? Or will it be handled by nextJS? 21 | // router.get('/signup', 22 | // (req, res) => { 23 | // return res.sendStatus(200); 24 | // }); 25 | 26 | // Checks if username is valid. Send error if not valid. Once successfully signed up, automatically logs in user and sends session cookie 27 | router.post('/signup', 28 | authController.signup, 29 | authController.generateCookie, 30 | (req, res) => { 31 | return res.status(200).json(createResponse(true, 200, 'Signup successful')); 32 | }); 33 | 34 | // end session and clear cookies middleware 35 | router.get('/logout', authController.logout, 36 | (req, res) => { 37 | return res.status(200).json(createResponse(true, 200, 'Logout successful')); 38 | }); 39 | 40 | module.exports = router; -------------------------------------------------------------------------------- /disco-db-demo/server/routers/userRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const userController = require('../controllers/userController') 4 | 5 | //commenting this out so nextjs routes to page without having to login/signup each time 6 | // router.get('/', 7 | // userController.getUserNotes, 8 | // (req, res) => { 9 | // return res.status(200).json(res.locals); 10 | // }); 11 | 12 | router.get('/load', 13 | userController.getUserNotes, 14 | (req, res) => { 15 | return res.status(200).json(res.locals); 16 | }); 17 | 18 | // Create a new entry in notes database. Send back unique id for the new entry 19 | router.post('/notes', 20 | userController.createAndGetNewNote, 21 | (req, res) => { 22 | return res.status(200).json(res.locals); 23 | }); 24 | 25 | // Modify an entry in notes database. Send back all data related to the updated entry 26 | router.patch('/notes', 27 | userController.modifyAndGetNote, 28 | (req, res) => { 29 | res.status(200).json(res.locals); 30 | }); 31 | 32 | // Delete an entry in notes database. 33 | router.delete('/notes', 34 | userController.deleteNote, 35 | (req, res) => { 36 | res.status(200).json({}); 37 | }); 38 | 39 | module.exports = router -------------------------------------------------------------------------------- /disco-db-demo/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const next = require('next'); 4 | 5 | const port = parseInt(process.env.PORT, 10) || 3000; 6 | const dev = process.env.NODE_ENV !== 'production'; 7 | const app = next({ dev }); 8 | const handle = app.getRequestHandler(); 9 | const db = require('./models/dbConnection.js'); 10 | 11 | const authRouter = require('./routers/authRouter'); 12 | const userRouter = require('./routers/userRouter'); 13 | const apiRouter = require('./routers/apiRouter'); 14 | const { sendData } = require('next/dist/server/api-utils'); 15 | 16 | 17 | app.prepare().then(() => { 18 | const server = express(); 19 | 20 | // Allows server to process incoming JSON, form data into the req.body, cookies 21 | server.use(express.json()); 22 | server.use(express.urlencoded({extended: true})); 23 | server.use(cookieParser()); // add cookieParser in when cookies implemented 24 | 25 | // establish connection to our MongoDB cluster 26 | // validate this is necessary while testing authRouter 27 | db.connection(); 28 | 29 | server.use(cookieParser()); 30 | server.use(express.json()); 31 | server.use('/auth', authRouter); 32 | server.use('/user', userRouter); 33 | server.use('/api', apiRouter); 34 | 35 | server.get('/', (req, res) => { 36 | // console.log('this is to the root endpoint'); 37 | return handle(req, res); 38 | }); 39 | 40 | server.all('*', (req, res) => { 41 | // console.log('this is a request to the server'); 42 | return handle(req, res); 43 | }); 44 | 45 | // local error handler 46 | server.use((req, res) => { 47 | res.status(404).send('Not Found') 48 | }); 49 | 50 | // global error handler 51 | server.use((err, req, res, next) => { 52 | const defaultErr = { 53 | log: 'Express error handler caught unknown middleware error', 54 | status: 500, 55 | message: { err: 'An error occurred' }, 56 | }; 57 | const errorObj = Object.assign({}, defaultErr, err); 58 | console.log(errorObj.log); 59 | return res.status(errorObj.status).json(errorObj.message); 60 | }); 61 | 62 | server.listen(port, (err) => { 63 | if (err) throw err; 64 | console.log(`> Ready on http://localhost:${port}`); 65 | }); 66 | }); 67 | 68 | module.exports = app; 69 | -------------------------------------------------------------------------------- /disco-db-demo/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /disco-db-demo/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /discodb/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 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. -------------------------------------------------------------------------------- /discodb/README.md: -------------------------------------------------------------------------------- 1 |

2 | DiscoDB Logo 3 |

4 | 5 | # DiscoDB: 6 | 7 | ## Table of Contents 8 | 9 | - [Features](#features) 10 | 11 | - [Installation](#installation) 12 | 13 | - [How It works](#how-it-works) 14 | 15 | - [Demo Apps](#demo-app) 16 | 17 | - [Contributing](#contributing) 18 | 19 | - [Authors](#authors) 20 | 21 | - [License](#license) 22 | 23 | ## Features 24 | 25 | - A minimalist IndexedDB (IDB) wrapper and syncing solution for when your application disco(nnects) from the network. 26 | - Lightweight with zero dependencies. 27 | - Functionalities can be implemented via Service Workers with minimal modification to client side code 28 | - Supports syncing IDB with NoSQL databases (MongoDB) with a unique keypath 29 | - Promise based wrapper for IDB to perform local CRUD operations while offline to provide seamless UX 30 | - Sync to main database via action queue with automatic network availability detection. 31 | 32 |
33 |
34 | Offline Capabilities 35 | 36 | ![](https://discodb.dev/wp-content/uploads/2022/03/Offline_capability2.gif) 37 |
38 | 39 |
40 | Dynamic Offline Data 41 | 42 | ![](https://discodb.dev/wp-content/uploads/2022/03/indexedDB_update.gif) 43 |
44 | 45 |
46 | Custom Action Queue 47 | 48 | ![](https://discodb.dev/wp-content/uploads/2022/03/actionqueue.gif) 49 |
50 |
51 | 52 | ## Installation 53 | 54 | ```bash 55 | npm install disco-db 56 | ``` 57 | ### Import the Library into the Service Worker 58 | Assuming a bundler such as Webpack, Rollup, etc is used: 59 | ```js 60 | import { discoConnect, discoSyncToServer, discoSyncOffline, discoSyncOnline, onlineUrlArr, offlineUrlArr, dbGlobals, idbPromise } from 'discodb'; 61 | ``` 62 | When registering the service worker, pass in an option object with property ``type:'module'`` 63 | ```js 64 | if("serviceWorker" in navigator) { 65 | window.addEventListener("load", function () { 66 | navigator.serviceWorker.register("sw.js", {type: 'module'}) 67 | ``` 68 | ## How It works 69 | 70 | ### Setting up the Config File 71 | Our library requires some minimal configuration. In the root directory of the project, create a file labeled ``discodb.config.js`` and update the values of the corresponding key property. 72 | ```js 73 | // discodb.config.js 74 | 75 | const dbGlobals = 76 | { 77 | version: "IDB version", 78 | databaseName: "IDB database name", 79 | storeName: "IDB Object Store name", 80 | syncQueue: "IDB Object Store Queue name", 81 | keypath: "Primary key of main database table", 82 | // Add all routes to be intercepted 83 | onlineRoutes: [ 84 | { 85 | url: "", 86 | } 87 | ], 88 | offlineRoutes: [ 89 | { 90 | url: " ", 91 | }, 92 | { 93 | url: " ", 94 | } 95 | ] 96 | } 97 | 98 | export { dbGlobals }; 99 | 100 | ``` 101 | 102 | ### DiscoSyncOffline & DiscoSyncOnline 103 | 104 | ``discoSyncOffline(method, url, clonedRequest)`` 105 | 106 | 1. ``discoSyncOffline`` is a request reducer that intercepts fetch requests and implements CRUD operations on the passed in endpoints. 107 | 108 | 2. ``discoSyncOffline`` takes in three parameters, the method and url of the ``event.request`` as well as a clone of the ``event.request``, utilizing the ``.clone()`` method. 109 | 110 | 3. Under the hood, ``discoSyncOffline`` will check the url, and perform a **GET**, **DELETE**, or **PATCH** operation with indexedDB and return a new **Response** object back to the client. 111 | 112 | 113 | ``discoSyncOnline(method, url, clonedResponse)`` 114 | 115 | 1. ``discoSyncOnline`` establishes a connection to indexedDB and populates the object store with your noSQL data. 116 | 117 | 2. ``discoSyncOnline`` takes in three paramaters, the method and url of the ``event.request`` as well as a clone of the ``response`` that was sent back from the server. 118 | 119 | 3. Under the hood, ``discoSyncOnline`` will check the url passed in and first clear indexedDB of any stale data, and repopulate with the response body that it received back from the server. 120 | 121 | 122 | ### Initializing IndexedDB Database 123 | To initialize an idb with attributes passed into ```discodb.config.js```, invoke ``` discoConnect()``` when installing the service worker 124 | ```js 125 | self.addEventListener('install', event => { 126 | discoConnect(); 127 | }); 128 | ``` 129 | 130 | ### Intercepting Event Requests 131 | ```discoSyncOffline()``` and ```discoSyncOnline()``` require the following to be true: 132 | 1. Network falling back to cache Service Worker strategy: [Documentation](https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker) 133 | 2. Response and Request objects passed into ```discoSyncOffline()``` and ```discoSyncOnline()``` respectively will need to be a cloned version. 134 | 135 | ```onlineUrlArr``` and ```offlineUrlArr``` will include the ```onlineRoutes``` and ```offlineRoutes``` from ```discodb.config.js ``` respectively in an Array. 136 | ```js 137 | self.addEventListener('fetch', event => { 138 | // clone the request 139 | const reqClone = event.request.clone(); 140 | event.respondWith( 141 | // network first approach 142 | fetch(event.request) 143 | .then((response) => { 144 | // clone the response 145 | const resCloneCache = response.clone(); 146 | const resCloneDB = response.clone() 147 | // open caches and store resCloneCache 148 | // ... 149 | // intercept routes included in onlineUrlArr 150 | if (onlineUrlArr.includes(url)){ 151 | discoSyncOnline(method, url, resCloneDB); 152 | } 153 | return response; 154 | }) 155 | // Fallback to Cache 156 | .catch((err) => { 157 | //invoke offline reducer to perform RUD functions to indexedDB 158 | if (offlineUrlArr.includes(url)){ 159 | return discoSyncOffline(method, url, reqClone); 160 | } 161 | // return cache 162 | // ... 163 | }) 164 | ) 165 | }); 166 | ``` 167 | 168 | ### Implementing the Action Queue Synchonization 169 | You can also use the synchronization queue separately from our reducers! Make sure to ``discoConnect()`` to the IDB database and have your configuration file ready. 170 | 1. Set up an event handler to listen for "***sync***" and a conditional to catch our tag, "***discoSync***". 171 | 1. Request for a synchronization event by registering through the service worker through ``discoRegisterSync()``. By default, the sync tag assigned to the request is '***discoSync***'. 172 | 1. Once a sync request has been registered, we now can add an object containing the HTTP request to the Object Store Queue by invoking ``discoAddToQueue(object)``.
173 | * Object format must contain these properties 174 | ```js 175 | { 176 | url: "Route URL", 177 | method: "HTTP Method", 178 | body: "data object from HTTP request" 179 | } 180 | ``` 181 | 1. Now that the Object Store Queue has been populated with HTTP requests, we can send them to the main server! Within the event handler for sync, invoke ``discoSyncToServer()``. 182 | ```js 183 | self.addEventListener('sync', (event) => { 184 | if(event.tag === 'discoSync'){ 185 | discoSyncToServer(); 186 | }; 187 | }; 188 | ``` 189 | 190 | ## Demo App 191 | Our demostration application utilizing this library is a simple note taking app. The demo app relies on service workers intercepting all HTTP requests for both online and offline requests. 192 | 193 | Fork and clone our repository onto your local repository and follow the README in the app. 194 | 195 | ## Contributing 196 | 197 | We'd love for you to test this library out and submit any issues you encounter. Also feel free to fork to your own repo and submit pull requests! 198 | 199 | ## Authors 200 |
201 | 202 | [Eric Gomez](https://github.com/ergomez0201) | [LinkedIn](https://www.linkedin.com/in/eric-gomez/) 203 |
204 | 205 | [Eric McCorkle](https://github.com/ericmccorkle) | [LinkedIn](https://www.linkedin.com/in/eric-mccorkle/) 206 |
207 | 208 | [Jackson Tong](https://github.com/jacksonktong) | [LinkedIn](www.linkedin.com/in/jacksonktong) 209 |
210 | 211 | [Young Min Lee](https://github.com/youngmineeh) | [LinkedIn](www.linkedin.com/in/youngminlee-) 212 | 213 |
214 | 215 | ## License 216 | 217 | DiscoDB is [MIT licensed](https://github.com/oslabs-beta/discodb/blob/main/LICENSE.md). 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /discodb/discoFunctions/backgroundSync.js: -------------------------------------------------------------------------------- 1 | import { idbPromise, dbGlobals } from '../../../discodb.config.js'; 2 | 3 | /** 4 | * @property {Function} accessObjectStore Access object store in IDB database and start a transaction 5 | * @param {String} storeName Object store to be accessed for transaction 6 | * @param {String} method Method for transaction, "readwrite, readonly" 7 | * @return {Object} Accessed Object store Object 8 | */ 9 | function accessObjectStore (storeName, method) { 10 | return idbPromise.DB.transaction([storeName], method).objectStore(storeName) 11 | }; 12 | /** 13 | * @property {Function} discoAddToQueue Adds Object into Object store 14 | * @param {Object} dataObject Objected to be added to Object store 15 | * 16 | */ 17 | function discoAddToQueue (dataObject) { 18 | //Open a transaction to object store 'Queue' 19 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite') 20 | //Add data to object store 21 | store.add(dataObject) 22 | }; 23 | /** 24 | * @property {Function} discoRegisterSync Request a "Sync" event to reattempt request when network is online. 25 | * 26 | */ 27 | async function discoRegisterSync() { 28 | try { 29 | const register = await registration.sync.register('discoSync'); 30 | return register; 31 | } catch(error) { 32 | console.log('Error:' , error); 33 | return error; 34 | } 35 | }; 36 | 37 | /** 38 | * @property {Function} discoSyncToServer Accesses the Queue Object Store and re-sends all requests saved in application/json. 39 | * 40 | */ 41 | function discoSyncToServer() { 42 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite'); 43 | const request = store.getAll(); 44 | 45 | request.onsuccess = function (event) { 46 | const httpQueue = event.target.result; 47 | //Comes back as an array of objects 48 | //Iterate Queue store and initialize Fetch request 49 | httpQueue.forEach((data) => { 50 | const { url, method, body } = data 51 | const headers = {'Content-Type': 'application/json'}; 52 | fetch(url, { 53 | method: method, 54 | headers: headers, 55 | body: JSON.stringify(body) 56 | }) 57 | .then((res) => res.json()) 58 | .then((res) => { 59 | //Previous transaction was closed due to getAll() 60 | //Reopen object store and delete the corresponding object on successful HTTP request 61 | const newStore = accessObjectStore(dbGlobals.syncQueue, 'readwrite'); 62 | newStore.delete(data.id); 63 | }) 64 | .catch((error) => { 65 | console.error('Failed to sync data to server:', error); 66 | throw error 67 | }) 68 | }); 69 | } 70 | request.onerror = (err) => { 71 | console.log('Attempt to sync queue failed:', err); 72 | } 73 | }; 74 | 75 | export { discoAddToQueue, discoRegisterSync, discoSyncToServer }; 76 | -------------------------------------------------------------------------------- /discodb/discoFunctions/discoGlobals.js: -------------------------------------------------------------------------------- 1 | import { dbGlobals } from '../../../discodb.config.js' 2 | 3 | const idbPromise = { 4 | DB: null 5 | } 6 | 7 | const onlineUrlArr = []; 8 | dbGlobals.onlineRoutes.forEach(el => { 9 | onlineUrlArr.push(el.url) 10 | }); 11 | 12 | const offlineUrlArr = []; 13 | dbGlobals.offlineRoutes.forEach(el => { 14 | offlineUrlArr.push(el.url); 15 | }); 16 | 17 | export { dbGlobals, idbPromise, onlineUrlArr, offlineUrlArr } -------------------------------------------------------------------------------- /discodb/discoFunctions/discoSync.js: -------------------------------------------------------------------------------- 1 | import { discoConnect, discoGetAll, discoDeleteOne, discoUpdateOne, discoAdd, discoDeleteAll } from './idbOperations.js'; 2 | import { discoAddToQueue, discoRegisterSync } from './backgroundSync.js'; 3 | import { idbPromise , dbGlobals } from './discoGlobals.js'; 4 | 5 | /** 6 | * @property {Function} discoSyncOffline Executes different IndexedDB logic based on the value of passed in method 7 | * @param {String} method This is the method property of the intercepted fetch request 8 | * @param {String} url This is the url property of the intercepted fetch request 9 | * @param {String} store This is the store property associated with the url provided in the config file 10 | * @param {Request} eventRequest This is the cloned version of the intercepted fetch request 11 | * 12 | */ 13 | function discoSyncOffline(method, url, clonedRequest) { 14 | switch(method) { 15 | case 'GET': 16 | if (idbPromise.DB) { 17 | return discoGetAll().then((data) => { 18 | const responseBody = { data }; 19 | const IDBData = new Response(JSON.stringify(responseBody)); 20 | return IDBData; 21 | }) 22 | } else { 23 | return discoConnect(() => { 24 | discoGetAll().then((data) => { 25 | const responseBody = {data: data}; 26 | const IDBData = new Response(JSON.stringify(responseBody)); 27 | return IDBData; 28 | }); 29 | }) 30 | } 31 | case 'DELETE': 32 | return clonedRequest.json() 33 | .then((data) => { 34 | const reqBody = { 35 | url: url, 36 | method: method, 37 | body: data 38 | }; 39 | discoRegisterSync(); 40 | discoAddToQueue(reqBody); 41 | const keypath = dbGlobals.keypath; 42 | const id = data[keypath]; 43 | discoDeleteOne(id); 44 | const deleteResponse = new Response(JSON.stringify({})); 45 | return deleteResponse; 46 | }) 47 | .catch( err => { 48 | console.log('Error in DELETE block: ', err); 49 | }) 50 | case 'PATCH': 51 | return clonedRequest.json() 52 | .then((data) => { 53 | const reqBody = { 54 | url: url, 55 | method: method, 56 | body: data 57 | }; 58 | discoRegisterSync(); 59 | discoAddToQueue(reqBody); 60 | const keypath = dbGlobals.keypath; 61 | const id = data[keypath]; 62 | discoUpdateOne(data); 63 | // returns empty object to trigger rerender in our app 64 | const patchResponse = new Response(JSON.stringify({})); 65 | return patchResponse; 66 | }) 67 | default: 68 | return caches.match(clonedRequest) 69 | .then(response => { 70 | return response 71 | }) 72 | } 73 | } 74 | 75 | /** 76 | * @property {Function} discoSyncOnline Establishes connection to indexedDB & create Object Stores as specified in Configuration. 77 | * @param {String} method This is the method property of the intercepted fetch request 78 | * @param {String} url This is the url property of the intercepted fetch request 79 | * @param {String} store This is the store property associated with the url provided in the config file 80 | * @param {Request} clonedResponse This is the cloned version of the intercepted fetch response 81 | * 82 | */ 83 | function discoSyncOnline(method, url, clonedResponse) { 84 | switch(method) { 85 | case 'GET': 86 | const resCloneDB = clonedResponse; 87 | resCloneDB.json().then(data => { 88 | if (idbPromise.DB) { 89 | discoDeleteAll(); 90 | } else { 91 | discoConnect( () => { 92 | discoDeleteAll(); 93 | }) 94 | } 95 | //populate indexedDB here 96 | data.data.forEach( note => { 97 | if (idbPromise.DB) { 98 | discoAdd(note); 99 | } else { 100 | discoConnect(() => { 101 | discoAdd(note); 102 | }) 103 | } 104 | }) 105 | }); 106 | break; 107 | default: 108 | break; 109 | } 110 | } 111 | 112 | export { discoSyncOffline, discoSyncOnline }; -------------------------------------------------------------------------------- /discodb/discoFunctions/idbOperations.js: -------------------------------------------------------------------------------- 1 | import { idbPromise, dbGlobals } from './discoGlobals.js'; 2 | 3 | /** 4 | * @property {Function} discoConnect Establishes connection to indexedDB & create Object Stores as specified in Configuration. 5 | * @param {Function} callback 6 | * 7 | */ 8 | function discoConnect(callback) { 9 | return new Promise((resolve, reject) => { 10 | let req = indexedDB.open(dbGlobals.databaseName, dbGlobals.version) 11 | req.onerror = (err) => { 12 | //could not open db 13 | console.log('Error: ', err); 14 | idbPromise.DB = null; 15 | reject(err); 16 | }; 17 | req.onupgradeneeded = (event) => { 18 | let db = event.target.result; 19 | // Database Version Upgraded 20 | if (!db.objectStoreNames.contains(dbGlobals.storeName)) { 21 | db.createObjectStore(dbGlobals.storeName, { 22 | keyPath: dbGlobals.keypath, 23 | }); 24 | } 25 | if (!db.objectStoreNames.contains(dbGlobals.syncQueue)) { 26 | //Creating Object Store for Sync Queue 27 | db.createObjectStore(dbGlobals.syncQueue, { 28 | keyPath: 'id', autoIncrement: true, 29 | }) 30 | } 31 | }; 32 | req.onsuccess = (event) => { 33 | idbPromise.DB = event.target.result; 34 | //Database connected 35 | if (callback) { 36 | callback(); 37 | } 38 | resolve(idbPromise.DB); 39 | }; 40 | }) 41 | }; 42 | 43 | /** 44 | * @property {Function} discoAdd Adds an object in to the configured Object Store. 45 | * @param {Object} dataObject Object data to be stored. 46 | * 47 | */ 48 | function discoAdd(dataObject) { 49 | return new Promise ( (resolve, reject) => { 50 | if (dataObject && idbPromise.DB) { 51 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 52 | 53 | tx.onerror = (err) => { 54 | console.log('Error:', err); 55 | reject(err); 56 | }; 57 | tx.oncomplete = (event) => { 58 | //Data added successfully 59 | }; 60 | 61 | let store = tx.objectStore(dbGlobals.storeName); 62 | let req = store.put(dataObject); 63 | 64 | req.onsuccess = (event) => { 65 | const result = event.target.result; 66 | resolve(result); 67 | }; 68 | } else { 69 | console.log('DB is not connected'); 70 | } 71 | }) 72 | }; 73 | 74 | /** 75 | * @property {Function} discoDeleteAll Deletes all properties of the configured Object Store. 76 | * 77 | */ 78 | function discoDeleteAll() { 79 | return new Promise( (resolve, reject) => { 80 | if (idbPromise.DB) { 81 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 82 | tx.onerror = (err) => { 83 | console.log('Error:', err); 84 | reject(err); 85 | }; 86 | tx.oncomplete = (event) => { 87 | // data deleted successfully 88 | }; 89 | let store = tx.objectStore(dbGlobals.storeName); 90 | const req = store.clear(); 91 | req.onsuccess = (event) => { 92 | const result = event.target.result; 93 | resolve(result); 94 | }; 95 | } else { 96 | console.log('DB is not connected'); 97 | } 98 | }) 99 | }; 100 | 101 | /** 102 | * @property {Function} discoGetAll Gets all properties saved on the configured Object Store. 103 | * @return {Array} array of objects containing all properties in the Object Store. 104 | */ 105 | function discoGetAll() { 106 | return new Promise((resolve, reject) => { 107 | if (idbPromise.DB) { 108 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readonly'); 109 | tx.onerror = (err) => { 110 | console.log('Error: ', err); 111 | reject(err); 112 | }; 113 | tx.oncomplete = (event) => { 114 | //Transaction successful, all objects retrieved. 115 | }; 116 | let store = tx.objectStore(dbGlobals.storeName); 117 | const req = store.getAll(); 118 | req.onsuccess = (event) => { 119 | const result = event.target.result; 120 | resolve(result); 121 | }; 122 | } else { 123 | console.log('DB is not connected'); 124 | } 125 | }) 126 | } 127 | 128 | /** 129 | * @property {Function} discoDeleteOne Deletes one property on the configured Object Store. 130 | * @param {String} id The id of the object on the configured Object Store * 131 | */ 132 | function discoDeleteOne(id) { 133 | return new Promise( (resolve, reject) => { 134 | if (idbPromise.DB) { 135 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 136 | tx.onerror = (err) => { 137 | console.log('Error: ', err); 138 | reject(err); 139 | }; 140 | tx.oncomplete = (event) => { 141 | //Transaction successful 142 | }; 143 | let store = tx.objectStore(dbGlobals.storeName); 144 | const req = store.delete(id); 145 | req.onsuccess = (event) => { 146 | const result = event.target.result; 147 | resolve(result); 148 | }; 149 | } else { 150 | console.log('DB is not connected'); 151 | } 152 | }) 153 | } 154 | 155 | /** 156 | * @property {Function} discoUpdateOne Updates one property on the configured Object Store. 157 | * @param {Object} dataObject The req body object on the configured Object Store 158 | * 159 | */ 160 | function discoUpdateOne(dataObject) { 161 | return new Promise ( (resolve, reject) => { 162 | if (idbPromise.DB) { 163 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite'); 164 | tx.onerror = (err) => { 165 | console.log('Error: ', err); 166 | reject(err); 167 | }; 168 | tx.oncomplete = (event) => { 169 | //Transaction successful 170 | }; 171 | let store = tx.objectStore(dbGlobals.storeName); 172 | const req = store.put(dataObject); 173 | req.onsuccess = (event) => { 174 | const result = event.target.result; 175 | resolve(result); 176 | }; 177 | } else { 178 | console.log('DB is not connected'); 179 | } 180 | }) 181 | }; 182 | 183 | export { discoConnect, discoAdd, discoDeleteAll, discoGetAll, discoDeleteOne, discoUpdateOne }; 184 | -------------------------------------------------------------------------------- /discodb/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | discoSyncOffline, 3 | discoSyncOnline 4 | } from './discoFunctions/discoSync'; 5 | 6 | import { 7 | discoConnect, 8 | discoAdd, 9 | discoDeleteAll, 10 | discoGetAll, 11 | discoDeleteOne, 12 | discoUpdateOne 13 | } from './discoFunctions/idbOperations'; 14 | 15 | import { 16 | discoAddToQueue, 17 | discoRegisterSync, 18 | discoSyncToServer 19 | } from './discoFunctions/backgroundSync'; 20 | 21 | export { 22 | discoSyncOffline, 23 | discoSyncOnline, 24 | discoConnect, 25 | discoAdd, 26 | discoDeleteAll, 27 | discoGetAll, 28 | discoDeleteOne, 29 | discoUpdateOne, 30 | discoAddToQueue, 31 | discoRegisterSync, 32 | discoSyncToServer } -------------------------------------------------------------------------------- /discodb/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disco-db", 3 | "version": "1.0.3", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "disco-db", 9 | "version": "1.0.3", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@rollup/plugin-commonjs": "^21.0.2" 13 | }, 14 | "devDependencies": { 15 | "@rollup/plugin-babel": "^5.3.1", 16 | "@rollup/plugin-node-resolve": "^13.1.3", 17 | "rollup": "^2.70.1", 18 | "rollup-plugin-node-polyfills": "^0.2.1", 19 | "rollup-plugin-terser": "^7.0.2" 20 | } 21 | }, 22 | "node_modules/@ampproject/remapping": { 23 | "version": "2.1.2", 24 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", 25 | "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", 26 | "dev": true, 27 | "peer": true, 28 | "dependencies": { 29 | "@jridgewell/trace-mapping": "^0.3.0" 30 | }, 31 | "engines": { 32 | "node": ">=6.0.0" 33 | } 34 | }, 35 | "node_modules/@babel/code-frame": { 36 | "version": "7.16.7", 37 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", 38 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", 39 | "dev": true, 40 | "dependencies": { 41 | "@babel/highlight": "^7.16.7" 42 | }, 43 | "engines": { 44 | "node": ">=6.9.0" 45 | } 46 | }, 47 | "node_modules/@babel/compat-data": { 48 | "version": "7.17.7", 49 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", 50 | "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", 51 | "dev": true, 52 | "peer": true, 53 | "engines": { 54 | "node": ">=6.9.0" 55 | } 56 | }, 57 | "node_modules/@babel/core": { 58 | "version": "7.17.7", 59 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz", 60 | "integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==", 61 | "dev": true, 62 | "peer": true, 63 | "dependencies": { 64 | "@ampproject/remapping": "^2.1.0", 65 | "@babel/code-frame": "^7.16.7", 66 | "@babel/generator": "^7.17.7", 67 | "@babel/helper-compilation-targets": "^7.17.7", 68 | "@babel/helper-module-transforms": "^7.17.7", 69 | "@babel/helpers": "^7.17.7", 70 | "@babel/parser": "^7.17.7", 71 | "@babel/template": "^7.16.7", 72 | "@babel/traverse": "^7.17.3", 73 | "@babel/types": "^7.17.0", 74 | "convert-source-map": "^1.7.0", 75 | "debug": "^4.1.0", 76 | "gensync": "^1.0.0-beta.2", 77 | "json5": "^2.1.2", 78 | "semver": "^6.3.0" 79 | }, 80 | "engines": { 81 | "node": ">=6.9.0" 82 | }, 83 | "funding": { 84 | "type": "opencollective", 85 | "url": "https://opencollective.com/babel" 86 | } 87 | }, 88 | "node_modules/@babel/generator": { 89 | "version": "7.17.7", 90 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", 91 | "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", 92 | "dev": true, 93 | "peer": true, 94 | "dependencies": { 95 | "@babel/types": "^7.17.0", 96 | "jsesc": "^2.5.1", 97 | "source-map": "^0.5.0" 98 | }, 99 | "engines": { 100 | "node": ">=6.9.0" 101 | } 102 | }, 103 | "node_modules/@babel/generator/node_modules/source-map": { 104 | "version": "0.5.7", 105 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 106 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 107 | "dev": true, 108 | "peer": true, 109 | "engines": { 110 | "node": ">=0.10.0" 111 | } 112 | }, 113 | "node_modules/@babel/helper-compilation-targets": { 114 | "version": "7.17.7", 115 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", 116 | "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", 117 | "dev": true, 118 | "peer": true, 119 | "dependencies": { 120 | "@babel/compat-data": "^7.17.7", 121 | "@babel/helper-validator-option": "^7.16.7", 122 | "browserslist": "^4.17.5", 123 | "semver": "^6.3.0" 124 | }, 125 | "engines": { 126 | "node": ">=6.9.0" 127 | }, 128 | "peerDependencies": { 129 | "@babel/core": "^7.0.0" 130 | } 131 | }, 132 | "node_modules/@babel/helper-environment-visitor": { 133 | "version": "7.16.7", 134 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", 135 | "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", 136 | "dev": true, 137 | "peer": true, 138 | "dependencies": { 139 | "@babel/types": "^7.16.7" 140 | }, 141 | "engines": { 142 | "node": ">=6.9.0" 143 | } 144 | }, 145 | "node_modules/@babel/helper-function-name": { 146 | "version": "7.16.7", 147 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", 148 | "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", 149 | "dev": true, 150 | "peer": true, 151 | "dependencies": { 152 | "@babel/helper-get-function-arity": "^7.16.7", 153 | "@babel/template": "^7.16.7", 154 | "@babel/types": "^7.16.7" 155 | }, 156 | "engines": { 157 | "node": ">=6.9.0" 158 | } 159 | }, 160 | "node_modules/@babel/helper-get-function-arity": { 161 | "version": "7.16.7", 162 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", 163 | "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", 164 | "dev": true, 165 | "peer": true, 166 | "dependencies": { 167 | "@babel/types": "^7.16.7" 168 | }, 169 | "engines": { 170 | "node": ">=6.9.0" 171 | } 172 | }, 173 | "node_modules/@babel/helper-hoist-variables": { 174 | "version": "7.16.7", 175 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", 176 | "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", 177 | "dev": true, 178 | "peer": true, 179 | "dependencies": { 180 | "@babel/types": "^7.16.7" 181 | }, 182 | "engines": { 183 | "node": ">=6.9.0" 184 | } 185 | }, 186 | "node_modules/@babel/helper-module-imports": { 187 | "version": "7.16.7", 188 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", 189 | "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", 190 | "dev": true, 191 | "dependencies": { 192 | "@babel/types": "^7.16.7" 193 | }, 194 | "engines": { 195 | "node": ">=6.9.0" 196 | } 197 | }, 198 | "node_modules/@babel/helper-module-transforms": { 199 | "version": "7.17.7", 200 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", 201 | "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", 202 | "dev": true, 203 | "peer": true, 204 | "dependencies": { 205 | "@babel/helper-environment-visitor": "^7.16.7", 206 | "@babel/helper-module-imports": "^7.16.7", 207 | "@babel/helper-simple-access": "^7.17.7", 208 | "@babel/helper-split-export-declaration": "^7.16.7", 209 | "@babel/helper-validator-identifier": "^7.16.7", 210 | "@babel/template": "^7.16.7", 211 | "@babel/traverse": "^7.17.3", 212 | "@babel/types": "^7.17.0" 213 | }, 214 | "engines": { 215 | "node": ">=6.9.0" 216 | } 217 | }, 218 | "node_modules/@babel/helper-simple-access": { 219 | "version": "7.17.7", 220 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", 221 | "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", 222 | "dev": true, 223 | "peer": true, 224 | "dependencies": { 225 | "@babel/types": "^7.17.0" 226 | }, 227 | "engines": { 228 | "node": ">=6.9.0" 229 | } 230 | }, 231 | "node_modules/@babel/helper-split-export-declaration": { 232 | "version": "7.16.7", 233 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", 234 | "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", 235 | "dev": true, 236 | "peer": true, 237 | "dependencies": { 238 | "@babel/types": "^7.16.7" 239 | }, 240 | "engines": { 241 | "node": ">=6.9.0" 242 | } 243 | }, 244 | "node_modules/@babel/helper-validator-identifier": { 245 | "version": "7.16.7", 246 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", 247 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", 248 | "dev": true, 249 | "engines": { 250 | "node": ">=6.9.0" 251 | } 252 | }, 253 | "node_modules/@babel/helper-validator-option": { 254 | "version": "7.16.7", 255 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", 256 | "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", 257 | "dev": true, 258 | "peer": true, 259 | "engines": { 260 | "node": ">=6.9.0" 261 | } 262 | }, 263 | "node_modules/@babel/helpers": { 264 | "version": "7.17.7", 265 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz", 266 | "integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==", 267 | "dev": true, 268 | "peer": true, 269 | "dependencies": { 270 | "@babel/template": "^7.16.7", 271 | "@babel/traverse": "^7.17.3", 272 | "@babel/types": "^7.17.0" 273 | }, 274 | "engines": { 275 | "node": ">=6.9.0" 276 | } 277 | }, 278 | "node_modules/@babel/highlight": { 279 | "version": "7.16.10", 280 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", 281 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", 282 | "dev": true, 283 | "dependencies": { 284 | "@babel/helper-validator-identifier": "^7.16.7", 285 | "chalk": "^2.0.0", 286 | "js-tokens": "^4.0.0" 287 | }, 288 | "engines": { 289 | "node": ">=6.9.0" 290 | } 291 | }, 292 | "node_modules/@babel/parser": { 293 | "version": "7.17.7", 294 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz", 295 | "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==", 296 | "dev": true, 297 | "peer": true, 298 | "bin": { 299 | "parser": "bin/babel-parser.js" 300 | }, 301 | "engines": { 302 | "node": ">=6.0.0" 303 | } 304 | }, 305 | "node_modules/@babel/template": { 306 | "version": "7.16.7", 307 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", 308 | "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", 309 | "dev": true, 310 | "peer": true, 311 | "dependencies": { 312 | "@babel/code-frame": "^7.16.7", 313 | "@babel/parser": "^7.16.7", 314 | "@babel/types": "^7.16.7" 315 | }, 316 | "engines": { 317 | "node": ">=6.9.0" 318 | } 319 | }, 320 | "node_modules/@babel/traverse": { 321 | "version": "7.17.3", 322 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", 323 | "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", 324 | "dev": true, 325 | "peer": true, 326 | "dependencies": { 327 | "@babel/code-frame": "^7.16.7", 328 | "@babel/generator": "^7.17.3", 329 | "@babel/helper-environment-visitor": "^7.16.7", 330 | "@babel/helper-function-name": "^7.16.7", 331 | "@babel/helper-hoist-variables": "^7.16.7", 332 | "@babel/helper-split-export-declaration": "^7.16.7", 333 | "@babel/parser": "^7.17.3", 334 | "@babel/types": "^7.17.0", 335 | "debug": "^4.1.0", 336 | "globals": "^11.1.0" 337 | }, 338 | "engines": { 339 | "node": ">=6.9.0" 340 | } 341 | }, 342 | "node_modules/@babel/types": { 343 | "version": "7.17.0", 344 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", 345 | "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", 346 | "dev": true, 347 | "dependencies": { 348 | "@babel/helper-validator-identifier": "^7.16.7", 349 | "to-fast-properties": "^2.0.0" 350 | }, 351 | "engines": { 352 | "node": ">=6.9.0" 353 | } 354 | }, 355 | "node_modules/@jridgewell/resolve-uri": { 356 | "version": "3.0.5", 357 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", 358 | "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", 359 | "dev": true, 360 | "peer": true, 361 | "engines": { 362 | "node": ">=6.0.0" 363 | } 364 | }, 365 | "node_modules/@jridgewell/sourcemap-codec": { 366 | "version": "1.4.11", 367 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", 368 | "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", 369 | "dev": true, 370 | "peer": true 371 | }, 372 | "node_modules/@jridgewell/trace-mapping": { 373 | "version": "0.3.4", 374 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", 375 | "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", 376 | "dev": true, 377 | "peer": true, 378 | "dependencies": { 379 | "@jridgewell/resolve-uri": "^3.0.3", 380 | "@jridgewell/sourcemap-codec": "^1.4.10" 381 | } 382 | }, 383 | "node_modules/@rollup/plugin-babel": { 384 | "version": "5.3.1", 385 | "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", 386 | "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", 387 | "dev": true, 388 | "dependencies": { 389 | "@babel/helper-module-imports": "^7.10.4", 390 | "@rollup/pluginutils": "^3.1.0" 391 | }, 392 | "engines": { 393 | "node": ">= 10.0.0" 394 | }, 395 | "peerDependencies": { 396 | "@babel/core": "^7.0.0", 397 | "@types/babel__core": "^7.1.9", 398 | "rollup": "^1.20.0||^2.0.0" 399 | }, 400 | "peerDependenciesMeta": { 401 | "@types/babel__core": { 402 | "optional": true 403 | } 404 | } 405 | }, 406 | "node_modules/@rollup/plugin-commonjs": { 407 | "version": "21.0.2", 408 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz", 409 | "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==", 410 | "dependencies": { 411 | "@rollup/pluginutils": "^3.1.0", 412 | "commondir": "^1.0.1", 413 | "estree-walker": "^2.0.1", 414 | "glob": "^7.1.6", 415 | "is-reference": "^1.2.1", 416 | "magic-string": "^0.25.7", 417 | "resolve": "^1.17.0" 418 | }, 419 | "engines": { 420 | "node": ">= 8.0.0" 421 | }, 422 | "peerDependencies": { 423 | "rollup": "^2.38.3" 424 | } 425 | }, 426 | "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { 427 | "version": "2.0.2", 428 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 429 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 430 | }, 431 | "node_modules/@rollup/plugin-node-resolve": { 432 | "version": "13.1.3", 433 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", 434 | "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", 435 | "dev": true, 436 | "dependencies": { 437 | "@rollup/pluginutils": "^3.1.0", 438 | "@types/resolve": "1.17.1", 439 | "builtin-modules": "^3.1.0", 440 | "deepmerge": "^4.2.2", 441 | "is-module": "^1.0.0", 442 | "resolve": "^1.19.0" 443 | }, 444 | "engines": { 445 | "node": ">= 10.0.0" 446 | }, 447 | "peerDependencies": { 448 | "rollup": "^2.42.0" 449 | } 450 | }, 451 | "node_modules/@rollup/pluginutils": { 452 | "version": "3.1.0", 453 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", 454 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", 455 | "dependencies": { 456 | "@types/estree": "0.0.39", 457 | "estree-walker": "^1.0.1", 458 | "picomatch": "^2.2.2" 459 | }, 460 | "engines": { 461 | "node": ">= 8.0.0" 462 | }, 463 | "peerDependencies": { 464 | "rollup": "^1.20.0||^2.0.0" 465 | } 466 | }, 467 | "node_modules/@types/estree": { 468 | "version": "0.0.39", 469 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 470 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" 471 | }, 472 | "node_modules/@types/node": { 473 | "version": "17.0.21", 474 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", 475 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", 476 | "dev": true 477 | }, 478 | "node_modules/@types/resolve": { 479 | "version": "1.17.1", 480 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", 481 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", 482 | "dev": true, 483 | "dependencies": { 484 | "@types/node": "*" 485 | } 486 | }, 487 | "node_modules/acorn": { 488 | "version": "8.7.0", 489 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 490 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", 491 | "dev": true, 492 | "bin": { 493 | "acorn": "bin/acorn" 494 | }, 495 | "engines": { 496 | "node": ">=0.4.0" 497 | } 498 | }, 499 | "node_modules/ansi-styles": { 500 | "version": "3.2.1", 501 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 502 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 503 | "dev": true, 504 | "dependencies": { 505 | "color-convert": "^1.9.0" 506 | }, 507 | "engines": { 508 | "node": ">=4" 509 | } 510 | }, 511 | "node_modules/balanced-match": { 512 | "version": "1.0.2", 513 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 514 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 515 | }, 516 | "node_modules/brace-expansion": { 517 | "version": "1.1.11", 518 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 519 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 520 | "dependencies": { 521 | "balanced-match": "^1.0.0", 522 | "concat-map": "0.0.1" 523 | } 524 | }, 525 | "node_modules/browserslist": { 526 | "version": "4.20.2", 527 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", 528 | "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", 529 | "dev": true, 530 | "funding": [ 531 | { 532 | "type": "opencollective", 533 | "url": "https://opencollective.com/browserslist" 534 | }, 535 | { 536 | "type": "tidelift", 537 | "url": "https://tidelift.com/funding/github/npm/browserslist" 538 | } 539 | ], 540 | "peer": true, 541 | "dependencies": { 542 | "caniuse-lite": "^1.0.30001317", 543 | "electron-to-chromium": "^1.4.84", 544 | "escalade": "^3.1.1", 545 | "node-releases": "^2.0.2", 546 | "picocolors": "^1.0.0" 547 | }, 548 | "bin": { 549 | "browserslist": "cli.js" 550 | }, 551 | "engines": { 552 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 553 | } 554 | }, 555 | "node_modules/buffer-from": { 556 | "version": "1.1.2", 557 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 558 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 559 | "dev": true 560 | }, 561 | "node_modules/builtin-modules": { 562 | "version": "3.2.0", 563 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", 564 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", 565 | "dev": true, 566 | "engines": { 567 | "node": ">=6" 568 | }, 569 | "funding": { 570 | "url": "https://github.com/sponsors/sindresorhus" 571 | } 572 | }, 573 | "node_modules/caniuse-lite": { 574 | "version": "1.0.30001317", 575 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz", 576 | "integrity": "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==", 577 | "dev": true, 578 | "peer": true, 579 | "funding": { 580 | "type": "opencollective", 581 | "url": "https://opencollective.com/browserslist" 582 | } 583 | }, 584 | "node_modules/chalk": { 585 | "version": "2.4.2", 586 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 587 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 588 | "dev": true, 589 | "dependencies": { 590 | "ansi-styles": "^3.2.1", 591 | "escape-string-regexp": "^1.0.5", 592 | "supports-color": "^5.3.0" 593 | }, 594 | "engines": { 595 | "node": ">=4" 596 | } 597 | }, 598 | "node_modules/color-convert": { 599 | "version": "1.9.3", 600 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 601 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 602 | "dev": true, 603 | "dependencies": { 604 | "color-name": "1.1.3" 605 | } 606 | }, 607 | "node_modules/color-name": { 608 | "version": "1.1.3", 609 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 610 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 611 | "dev": true 612 | }, 613 | "node_modules/commander": { 614 | "version": "2.20.3", 615 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 616 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 617 | "dev": true 618 | }, 619 | "node_modules/commondir": { 620 | "version": "1.0.1", 621 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 622 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" 623 | }, 624 | "node_modules/concat-map": { 625 | "version": "0.0.1", 626 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 627 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 628 | }, 629 | "node_modules/convert-source-map": { 630 | "version": "1.8.0", 631 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", 632 | "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", 633 | "dev": true, 634 | "peer": true, 635 | "dependencies": { 636 | "safe-buffer": "~5.1.1" 637 | } 638 | }, 639 | "node_modules/convert-source-map/node_modules/safe-buffer": { 640 | "version": "5.1.2", 641 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 642 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 643 | "dev": true, 644 | "peer": true 645 | }, 646 | "node_modules/debug": { 647 | "version": "4.3.3", 648 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 649 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 650 | "dev": true, 651 | "peer": true, 652 | "dependencies": { 653 | "ms": "2.1.2" 654 | }, 655 | "engines": { 656 | "node": ">=6.0" 657 | }, 658 | "peerDependenciesMeta": { 659 | "supports-color": { 660 | "optional": true 661 | } 662 | } 663 | }, 664 | "node_modules/deepmerge": { 665 | "version": "4.2.2", 666 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 667 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 668 | "dev": true, 669 | "engines": { 670 | "node": ">=0.10.0" 671 | } 672 | }, 673 | "node_modules/electron-to-chromium": { 674 | "version": "1.4.86", 675 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz", 676 | "integrity": "sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w==", 677 | "dev": true, 678 | "peer": true 679 | }, 680 | "node_modules/escalade": { 681 | "version": "3.1.1", 682 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 683 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 684 | "dev": true, 685 | "peer": true, 686 | "engines": { 687 | "node": ">=6" 688 | } 689 | }, 690 | "node_modules/escape-string-regexp": { 691 | "version": "1.0.5", 692 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 693 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 694 | "dev": true, 695 | "engines": { 696 | "node": ">=0.8.0" 697 | } 698 | }, 699 | "node_modules/estree-walker": { 700 | "version": "1.0.1", 701 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", 702 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" 703 | }, 704 | "node_modules/fs.realpath": { 705 | "version": "1.0.0", 706 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 707 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 708 | }, 709 | "node_modules/fsevents": { 710 | "version": "2.3.2", 711 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 712 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 713 | "hasInstallScript": true, 714 | "optional": true, 715 | "os": [ 716 | "darwin" 717 | ], 718 | "engines": { 719 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 720 | } 721 | }, 722 | "node_modules/function-bind": { 723 | "version": "1.1.1", 724 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 725 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 726 | }, 727 | "node_modules/gensync": { 728 | "version": "1.0.0-beta.2", 729 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 730 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 731 | "dev": true, 732 | "peer": true, 733 | "engines": { 734 | "node": ">=6.9.0" 735 | } 736 | }, 737 | "node_modules/glob": { 738 | "version": "7.2.0", 739 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 740 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 741 | "dependencies": { 742 | "fs.realpath": "^1.0.0", 743 | "inflight": "^1.0.4", 744 | "inherits": "2", 745 | "minimatch": "^3.0.4", 746 | "once": "^1.3.0", 747 | "path-is-absolute": "^1.0.0" 748 | }, 749 | "engines": { 750 | "node": "*" 751 | }, 752 | "funding": { 753 | "url": "https://github.com/sponsors/isaacs" 754 | } 755 | }, 756 | "node_modules/globals": { 757 | "version": "11.12.0", 758 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 759 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 760 | "dev": true, 761 | "peer": true, 762 | "engines": { 763 | "node": ">=4" 764 | } 765 | }, 766 | "node_modules/has": { 767 | "version": "1.0.3", 768 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 769 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 770 | "dependencies": { 771 | "function-bind": "^1.1.1" 772 | }, 773 | "engines": { 774 | "node": ">= 0.4.0" 775 | } 776 | }, 777 | "node_modules/has-flag": { 778 | "version": "3.0.0", 779 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 780 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 781 | "dev": true, 782 | "engines": { 783 | "node": ">=4" 784 | } 785 | }, 786 | "node_modules/inflight": { 787 | "version": "1.0.6", 788 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 789 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 790 | "dependencies": { 791 | "once": "^1.3.0", 792 | "wrappy": "1" 793 | } 794 | }, 795 | "node_modules/inherits": { 796 | "version": "2.0.4", 797 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 798 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 799 | }, 800 | "node_modules/is-core-module": { 801 | "version": "2.8.1", 802 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", 803 | "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", 804 | "dependencies": { 805 | "has": "^1.0.3" 806 | }, 807 | "funding": { 808 | "url": "https://github.com/sponsors/ljharb" 809 | } 810 | }, 811 | "node_modules/is-module": { 812 | "version": "1.0.0", 813 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 814 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 815 | "dev": true 816 | }, 817 | "node_modules/is-reference": { 818 | "version": "1.2.1", 819 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", 820 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", 821 | "dependencies": { 822 | "@types/estree": "*" 823 | } 824 | }, 825 | "node_modules/jest-worker": { 826 | "version": "26.6.2", 827 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", 828 | "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", 829 | "dev": true, 830 | "dependencies": { 831 | "@types/node": "*", 832 | "merge-stream": "^2.0.0", 833 | "supports-color": "^7.0.0" 834 | }, 835 | "engines": { 836 | "node": ">= 10.13.0" 837 | } 838 | }, 839 | "node_modules/jest-worker/node_modules/has-flag": { 840 | "version": "4.0.0", 841 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 842 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 843 | "dev": true, 844 | "engines": { 845 | "node": ">=8" 846 | } 847 | }, 848 | "node_modules/jest-worker/node_modules/supports-color": { 849 | "version": "7.2.0", 850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 851 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 852 | "dev": true, 853 | "dependencies": { 854 | "has-flag": "^4.0.0" 855 | }, 856 | "engines": { 857 | "node": ">=8" 858 | } 859 | }, 860 | "node_modules/js-tokens": { 861 | "version": "4.0.0", 862 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 863 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 864 | "dev": true 865 | }, 866 | "node_modules/jsesc": { 867 | "version": "2.5.2", 868 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 869 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 870 | "dev": true, 871 | "peer": true, 872 | "bin": { 873 | "jsesc": "bin/jsesc" 874 | }, 875 | "engines": { 876 | "node": ">=4" 877 | } 878 | }, 879 | "node_modules/json5": { 880 | "version": "2.2.0", 881 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", 882 | "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", 883 | "dev": true, 884 | "peer": true, 885 | "dependencies": { 886 | "minimist": "^1.2.5" 887 | }, 888 | "bin": { 889 | "json5": "lib/cli.js" 890 | }, 891 | "engines": { 892 | "node": ">=6" 893 | } 894 | }, 895 | "node_modules/magic-string": { 896 | "version": "0.25.9", 897 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", 898 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", 899 | "dependencies": { 900 | "sourcemap-codec": "^1.4.8" 901 | } 902 | }, 903 | "node_modules/merge-stream": { 904 | "version": "2.0.0", 905 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 906 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 907 | "dev": true 908 | }, 909 | "node_modules/minimatch": { 910 | "version": "3.1.2", 911 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 912 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 913 | "dependencies": { 914 | "brace-expansion": "^1.1.7" 915 | }, 916 | "engines": { 917 | "node": "*" 918 | } 919 | }, 920 | "node_modules/minimist": { 921 | "version": "1.2.5", 922 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 923 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 924 | "dev": true, 925 | "peer": true 926 | }, 927 | "node_modules/ms": { 928 | "version": "2.1.2", 929 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 930 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 931 | "dev": true, 932 | "peer": true 933 | }, 934 | "node_modules/node-releases": { 935 | "version": "2.0.2", 936 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", 937 | "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", 938 | "dev": true, 939 | "peer": true 940 | }, 941 | "node_modules/once": { 942 | "version": "1.4.0", 943 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 944 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 945 | "dependencies": { 946 | "wrappy": "1" 947 | } 948 | }, 949 | "node_modules/path-is-absolute": { 950 | "version": "1.0.1", 951 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 952 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 953 | "engines": { 954 | "node": ">=0.10.0" 955 | } 956 | }, 957 | "node_modules/path-parse": { 958 | "version": "1.0.7", 959 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 960 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 961 | }, 962 | "node_modules/picocolors": { 963 | "version": "1.0.0", 964 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 965 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 966 | "dev": true, 967 | "peer": true 968 | }, 969 | "node_modules/picomatch": { 970 | "version": "2.3.1", 971 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 972 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 973 | "engines": { 974 | "node": ">=8.6" 975 | }, 976 | "funding": { 977 | "url": "https://github.com/sponsors/jonschlinkert" 978 | } 979 | }, 980 | "node_modules/randombytes": { 981 | "version": "2.1.0", 982 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 983 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 984 | "dev": true, 985 | "dependencies": { 986 | "safe-buffer": "^5.1.0" 987 | } 988 | }, 989 | "node_modules/resolve": { 990 | "version": "1.22.0", 991 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", 992 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", 993 | "dependencies": { 994 | "is-core-module": "^2.8.1", 995 | "path-parse": "^1.0.7", 996 | "supports-preserve-symlinks-flag": "^1.0.0" 997 | }, 998 | "bin": { 999 | "resolve": "bin/resolve" 1000 | }, 1001 | "funding": { 1002 | "url": "https://github.com/sponsors/ljharb" 1003 | } 1004 | }, 1005 | "node_modules/rollup": { 1006 | "version": "2.70.1", 1007 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz", 1008 | "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==", 1009 | "bin": { 1010 | "rollup": "dist/bin/rollup" 1011 | }, 1012 | "engines": { 1013 | "node": ">=10.0.0" 1014 | }, 1015 | "optionalDependencies": { 1016 | "fsevents": "~2.3.2" 1017 | } 1018 | }, 1019 | "node_modules/rollup-plugin-inject": { 1020 | "version": "3.0.2", 1021 | "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", 1022 | "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", 1023 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", 1024 | "dev": true, 1025 | "dependencies": { 1026 | "estree-walker": "^0.6.1", 1027 | "magic-string": "^0.25.3", 1028 | "rollup-pluginutils": "^2.8.1" 1029 | } 1030 | }, 1031 | "node_modules/rollup-plugin-inject/node_modules/estree-walker": { 1032 | "version": "0.6.1", 1033 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 1034 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 1035 | "dev": true 1036 | }, 1037 | "node_modules/rollup-plugin-node-polyfills": { 1038 | "version": "0.2.1", 1039 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", 1040 | "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", 1041 | "dev": true, 1042 | "dependencies": { 1043 | "rollup-plugin-inject": "^3.0.0" 1044 | } 1045 | }, 1046 | "node_modules/rollup-plugin-terser": { 1047 | "version": "7.0.2", 1048 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", 1049 | "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", 1050 | "dev": true, 1051 | "dependencies": { 1052 | "@babel/code-frame": "^7.10.4", 1053 | "jest-worker": "^26.2.1", 1054 | "serialize-javascript": "^4.0.0", 1055 | "terser": "^5.0.0" 1056 | }, 1057 | "peerDependencies": { 1058 | "rollup": "^2.0.0" 1059 | } 1060 | }, 1061 | "node_modules/rollup-pluginutils": { 1062 | "version": "2.8.2", 1063 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 1064 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 1065 | "dev": true, 1066 | "dependencies": { 1067 | "estree-walker": "^0.6.1" 1068 | } 1069 | }, 1070 | "node_modules/rollup-pluginutils/node_modules/estree-walker": { 1071 | "version": "0.6.1", 1072 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 1073 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 1074 | "dev": true 1075 | }, 1076 | "node_modules/safe-buffer": { 1077 | "version": "5.2.1", 1078 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1079 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1080 | "dev": true, 1081 | "funding": [ 1082 | { 1083 | "type": "github", 1084 | "url": "https://github.com/sponsors/feross" 1085 | }, 1086 | { 1087 | "type": "patreon", 1088 | "url": "https://www.patreon.com/feross" 1089 | }, 1090 | { 1091 | "type": "consulting", 1092 | "url": "https://feross.org/support" 1093 | } 1094 | ] 1095 | }, 1096 | "node_modules/semver": { 1097 | "version": "6.3.0", 1098 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 1099 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 1100 | "dev": true, 1101 | "peer": true, 1102 | "bin": { 1103 | "semver": "bin/semver.js" 1104 | } 1105 | }, 1106 | "node_modules/serialize-javascript": { 1107 | "version": "4.0.0", 1108 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", 1109 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", 1110 | "dev": true, 1111 | "dependencies": { 1112 | "randombytes": "^2.1.0" 1113 | } 1114 | }, 1115 | "node_modules/source-map": { 1116 | "version": "0.7.3", 1117 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 1118 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 1119 | "dev": true, 1120 | "engines": { 1121 | "node": ">= 8" 1122 | } 1123 | }, 1124 | "node_modules/source-map-support": { 1125 | "version": "0.5.21", 1126 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 1127 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 1128 | "dev": true, 1129 | "dependencies": { 1130 | "buffer-from": "^1.0.0", 1131 | "source-map": "^0.6.0" 1132 | } 1133 | }, 1134 | "node_modules/source-map-support/node_modules/source-map": { 1135 | "version": "0.6.1", 1136 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1137 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1138 | "dev": true, 1139 | "engines": { 1140 | "node": ">=0.10.0" 1141 | } 1142 | }, 1143 | "node_modules/sourcemap-codec": { 1144 | "version": "1.4.8", 1145 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 1146 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" 1147 | }, 1148 | "node_modules/supports-color": { 1149 | "version": "5.5.0", 1150 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1151 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1152 | "dev": true, 1153 | "dependencies": { 1154 | "has-flag": "^3.0.0" 1155 | }, 1156 | "engines": { 1157 | "node": ">=4" 1158 | } 1159 | }, 1160 | "node_modules/supports-preserve-symlinks-flag": { 1161 | "version": "1.0.0", 1162 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1163 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1164 | "engines": { 1165 | "node": ">= 0.4" 1166 | }, 1167 | "funding": { 1168 | "url": "https://github.com/sponsors/ljharb" 1169 | } 1170 | }, 1171 | "node_modules/terser": { 1172 | "version": "5.12.1", 1173 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", 1174 | "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", 1175 | "dev": true, 1176 | "dependencies": { 1177 | "acorn": "^8.5.0", 1178 | "commander": "^2.20.0", 1179 | "source-map": "~0.7.2", 1180 | "source-map-support": "~0.5.20" 1181 | }, 1182 | "bin": { 1183 | "terser": "bin/terser" 1184 | }, 1185 | "engines": { 1186 | "node": ">=10" 1187 | } 1188 | }, 1189 | "node_modules/to-fast-properties": { 1190 | "version": "2.0.0", 1191 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1192 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 1193 | "dev": true, 1194 | "engines": { 1195 | "node": ">=4" 1196 | } 1197 | }, 1198 | "node_modules/wrappy": { 1199 | "version": "1.0.2", 1200 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1201 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1202 | } 1203 | }, 1204 | "dependencies": { 1205 | "@ampproject/remapping": { 1206 | "version": "2.1.2", 1207 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", 1208 | "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", 1209 | "dev": true, 1210 | "peer": true, 1211 | "requires": { 1212 | "@jridgewell/trace-mapping": "^0.3.0" 1213 | } 1214 | }, 1215 | "@babel/code-frame": { 1216 | "version": "7.16.7", 1217 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", 1218 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", 1219 | "dev": true, 1220 | "requires": { 1221 | "@babel/highlight": "^7.16.7" 1222 | } 1223 | }, 1224 | "@babel/compat-data": { 1225 | "version": "7.17.7", 1226 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", 1227 | "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", 1228 | "dev": true, 1229 | "peer": true 1230 | }, 1231 | "@babel/core": { 1232 | "version": "7.17.7", 1233 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz", 1234 | "integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==", 1235 | "dev": true, 1236 | "peer": true, 1237 | "requires": { 1238 | "@ampproject/remapping": "^2.1.0", 1239 | "@babel/code-frame": "^7.16.7", 1240 | "@babel/generator": "^7.17.7", 1241 | "@babel/helper-compilation-targets": "^7.17.7", 1242 | "@babel/helper-module-transforms": "^7.17.7", 1243 | "@babel/helpers": "^7.17.7", 1244 | "@babel/parser": "^7.17.7", 1245 | "@babel/template": "^7.16.7", 1246 | "@babel/traverse": "^7.17.3", 1247 | "@babel/types": "^7.17.0", 1248 | "convert-source-map": "^1.7.0", 1249 | "debug": "^4.1.0", 1250 | "gensync": "^1.0.0-beta.2", 1251 | "json5": "^2.1.2", 1252 | "semver": "^6.3.0" 1253 | } 1254 | }, 1255 | "@babel/generator": { 1256 | "version": "7.17.7", 1257 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", 1258 | "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", 1259 | "dev": true, 1260 | "peer": true, 1261 | "requires": { 1262 | "@babel/types": "^7.17.0", 1263 | "jsesc": "^2.5.1", 1264 | "source-map": "^0.5.0" 1265 | }, 1266 | "dependencies": { 1267 | "source-map": { 1268 | "version": "0.5.7", 1269 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1270 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1271 | "dev": true, 1272 | "peer": true 1273 | } 1274 | } 1275 | }, 1276 | "@babel/helper-compilation-targets": { 1277 | "version": "7.17.7", 1278 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", 1279 | "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", 1280 | "dev": true, 1281 | "peer": true, 1282 | "requires": { 1283 | "@babel/compat-data": "^7.17.7", 1284 | "@babel/helper-validator-option": "^7.16.7", 1285 | "browserslist": "^4.17.5", 1286 | "semver": "^6.3.0" 1287 | } 1288 | }, 1289 | "@babel/helper-environment-visitor": { 1290 | "version": "7.16.7", 1291 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", 1292 | "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", 1293 | "dev": true, 1294 | "peer": true, 1295 | "requires": { 1296 | "@babel/types": "^7.16.7" 1297 | } 1298 | }, 1299 | "@babel/helper-function-name": { 1300 | "version": "7.16.7", 1301 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", 1302 | "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", 1303 | "dev": true, 1304 | "peer": true, 1305 | "requires": { 1306 | "@babel/helper-get-function-arity": "^7.16.7", 1307 | "@babel/template": "^7.16.7", 1308 | "@babel/types": "^7.16.7" 1309 | } 1310 | }, 1311 | "@babel/helper-get-function-arity": { 1312 | "version": "7.16.7", 1313 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", 1314 | "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", 1315 | "dev": true, 1316 | "peer": true, 1317 | "requires": { 1318 | "@babel/types": "^7.16.7" 1319 | } 1320 | }, 1321 | "@babel/helper-hoist-variables": { 1322 | "version": "7.16.7", 1323 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", 1324 | "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", 1325 | "dev": true, 1326 | "peer": true, 1327 | "requires": { 1328 | "@babel/types": "^7.16.7" 1329 | } 1330 | }, 1331 | "@babel/helper-module-imports": { 1332 | "version": "7.16.7", 1333 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", 1334 | "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", 1335 | "dev": true, 1336 | "requires": { 1337 | "@babel/types": "^7.16.7" 1338 | } 1339 | }, 1340 | "@babel/helper-module-transforms": { 1341 | "version": "7.17.7", 1342 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", 1343 | "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", 1344 | "dev": true, 1345 | "peer": true, 1346 | "requires": { 1347 | "@babel/helper-environment-visitor": "^7.16.7", 1348 | "@babel/helper-module-imports": "^7.16.7", 1349 | "@babel/helper-simple-access": "^7.17.7", 1350 | "@babel/helper-split-export-declaration": "^7.16.7", 1351 | "@babel/helper-validator-identifier": "^7.16.7", 1352 | "@babel/template": "^7.16.7", 1353 | "@babel/traverse": "^7.17.3", 1354 | "@babel/types": "^7.17.0" 1355 | } 1356 | }, 1357 | "@babel/helper-simple-access": { 1358 | "version": "7.17.7", 1359 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", 1360 | "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", 1361 | "dev": true, 1362 | "peer": true, 1363 | "requires": { 1364 | "@babel/types": "^7.17.0" 1365 | } 1366 | }, 1367 | "@babel/helper-split-export-declaration": { 1368 | "version": "7.16.7", 1369 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", 1370 | "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", 1371 | "dev": true, 1372 | "peer": true, 1373 | "requires": { 1374 | "@babel/types": "^7.16.7" 1375 | } 1376 | }, 1377 | "@babel/helper-validator-identifier": { 1378 | "version": "7.16.7", 1379 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", 1380 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", 1381 | "dev": true 1382 | }, 1383 | "@babel/helper-validator-option": { 1384 | "version": "7.16.7", 1385 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", 1386 | "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", 1387 | "dev": true, 1388 | "peer": true 1389 | }, 1390 | "@babel/helpers": { 1391 | "version": "7.17.7", 1392 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz", 1393 | "integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==", 1394 | "dev": true, 1395 | "peer": true, 1396 | "requires": { 1397 | "@babel/template": "^7.16.7", 1398 | "@babel/traverse": "^7.17.3", 1399 | "@babel/types": "^7.17.0" 1400 | } 1401 | }, 1402 | "@babel/highlight": { 1403 | "version": "7.16.10", 1404 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", 1405 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", 1406 | "dev": true, 1407 | "requires": { 1408 | "@babel/helper-validator-identifier": "^7.16.7", 1409 | "chalk": "^2.0.0", 1410 | "js-tokens": "^4.0.0" 1411 | } 1412 | }, 1413 | "@babel/parser": { 1414 | "version": "7.17.7", 1415 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz", 1416 | "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==", 1417 | "dev": true, 1418 | "peer": true 1419 | }, 1420 | "@babel/template": { 1421 | "version": "7.16.7", 1422 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", 1423 | "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", 1424 | "dev": true, 1425 | "peer": true, 1426 | "requires": { 1427 | "@babel/code-frame": "^7.16.7", 1428 | "@babel/parser": "^7.16.7", 1429 | "@babel/types": "^7.16.7" 1430 | } 1431 | }, 1432 | "@babel/traverse": { 1433 | "version": "7.17.3", 1434 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", 1435 | "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", 1436 | "dev": true, 1437 | "peer": true, 1438 | "requires": { 1439 | "@babel/code-frame": "^7.16.7", 1440 | "@babel/generator": "^7.17.3", 1441 | "@babel/helper-environment-visitor": "^7.16.7", 1442 | "@babel/helper-function-name": "^7.16.7", 1443 | "@babel/helper-hoist-variables": "^7.16.7", 1444 | "@babel/helper-split-export-declaration": "^7.16.7", 1445 | "@babel/parser": "^7.17.3", 1446 | "@babel/types": "^7.17.0", 1447 | "debug": "^4.1.0", 1448 | "globals": "^11.1.0" 1449 | } 1450 | }, 1451 | "@babel/types": { 1452 | "version": "7.17.0", 1453 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", 1454 | "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", 1455 | "dev": true, 1456 | "requires": { 1457 | "@babel/helper-validator-identifier": "^7.16.7", 1458 | "to-fast-properties": "^2.0.0" 1459 | } 1460 | }, 1461 | "@jridgewell/resolve-uri": { 1462 | "version": "3.0.5", 1463 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", 1464 | "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", 1465 | "dev": true, 1466 | "peer": true 1467 | }, 1468 | "@jridgewell/sourcemap-codec": { 1469 | "version": "1.4.11", 1470 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", 1471 | "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", 1472 | "dev": true, 1473 | "peer": true 1474 | }, 1475 | "@jridgewell/trace-mapping": { 1476 | "version": "0.3.4", 1477 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", 1478 | "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", 1479 | "dev": true, 1480 | "peer": true, 1481 | "requires": { 1482 | "@jridgewell/resolve-uri": "^3.0.3", 1483 | "@jridgewell/sourcemap-codec": "^1.4.10" 1484 | } 1485 | }, 1486 | "@rollup/plugin-babel": { 1487 | "version": "5.3.1", 1488 | "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", 1489 | "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", 1490 | "dev": true, 1491 | "requires": { 1492 | "@babel/helper-module-imports": "^7.10.4", 1493 | "@rollup/pluginutils": "^3.1.0" 1494 | } 1495 | }, 1496 | "@rollup/plugin-commonjs": { 1497 | "version": "21.0.2", 1498 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz", 1499 | "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==", 1500 | "requires": { 1501 | "@rollup/pluginutils": "^3.1.0", 1502 | "commondir": "^1.0.1", 1503 | "estree-walker": "^2.0.1", 1504 | "glob": "^7.1.6", 1505 | "is-reference": "^1.2.1", 1506 | "magic-string": "^0.25.7", 1507 | "resolve": "^1.17.0" 1508 | }, 1509 | "dependencies": { 1510 | "estree-walker": { 1511 | "version": "2.0.2", 1512 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1513 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 1514 | } 1515 | } 1516 | }, 1517 | "@rollup/plugin-node-resolve": { 1518 | "version": "13.1.3", 1519 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", 1520 | "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", 1521 | "dev": true, 1522 | "requires": { 1523 | "@rollup/pluginutils": "^3.1.0", 1524 | "@types/resolve": "1.17.1", 1525 | "builtin-modules": "^3.1.0", 1526 | "deepmerge": "^4.2.2", 1527 | "is-module": "^1.0.0", 1528 | "resolve": "^1.19.0" 1529 | } 1530 | }, 1531 | "@rollup/pluginutils": { 1532 | "version": "3.1.0", 1533 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", 1534 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", 1535 | "requires": { 1536 | "@types/estree": "0.0.39", 1537 | "estree-walker": "^1.0.1", 1538 | "picomatch": "^2.2.2" 1539 | } 1540 | }, 1541 | "@types/estree": { 1542 | "version": "0.0.39", 1543 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 1544 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" 1545 | }, 1546 | "@types/node": { 1547 | "version": "17.0.21", 1548 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", 1549 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", 1550 | "dev": true 1551 | }, 1552 | "@types/resolve": { 1553 | "version": "1.17.1", 1554 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", 1555 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", 1556 | "dev": true, 1557 | "requires": { 1558 | "@types/node": "*" 1559 | } 1560 | }, 1561 | "acorn": { 1562 | "version": "8.7.0", 1563 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 1564 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", 1565 | "dev": true 1566 | }, 1567 | "ansi-styles": { 1568 | "version": "3.2.1", 1569 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 1570 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 1571 | "dev": true, 1572 | "requires": { 1573 | "color-convert": "^1.9.0" 1574 | } 1575 | }, 1576 | "balanced-match": { 1577 | "version": "1.0.2", 1578 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1579 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 1580 | }, 1581 | "brace-expansion": { 1582 | "version": "1.1.11", 1583 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1584 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1585 | "requires": { 1586 | "balanced-match": "^1.0.0", 1587 | "concat-map": "0.0.1" 1588 | } 1589 | }, 1590 | "browserslist": { 1591 | "version": "4.20.2", 1592 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", 1593 | "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", 1594 | "dev": true, 1595 | "peer": true, 1596 | "requires": { 1597 | "caniuse-lite": "^1.0.30001317", 1598 | "electron-to-chromium": "^1.4.84", 1599 | "escalade": "^3.1.1", 1600 | "node-releases": "^2.0.2", 1601 | "picocolors": "^1.0.0" 1602 | } 1603 | }, 1604 | "buffer-from": { 1605 | "version": "1.1.2", 1606 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 1607 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 1608 | "dev": true 1609 | }, 1610 | "builtin-modules": { 1611 | "version": "3.2.0", 1612 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", 1613 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", 1614 | "dev": true 1615 | }, 1616 | "caniuse-lite": { 1617 | "version": "1.0.30001317", 1618 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz", 1619 | "integrity": "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==", 1620 | "dev": true, 1621 | "peer": true 1622 | }, 1623 | "chalk": { 1624 | "version": "2.4.2", 1625 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 1626 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 1627 | "dev": true, 1628 | "requires": { 1629 | "ansi-styles": "^3.2.1", 1630 | "escape-string-regexp": "^1.0.5", 1631 | "supports-color": "^5.3.0" 1632 | } 1633 | }, 1634 | "color-convert": { 1635 | "version": "1.9.3", 1636 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 1637 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 1638 | "dev": true, 1639 | "requires": { 1640 | "color-name": "1.1.3" 1641 | } 1642 | }, 1643 | "color-name": { 1644 | "version": "1.1.3", 1645 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 1646 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 1647 | "dev": true 1648 | }, 1649 | "commander": { 1650 | "version": "2.20.3", 1651 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 1652 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 1653 | "dev": true 1654 | }, 1655 | "commondir": { 1656 | "version": "1.0.1", 1657 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 1658 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" 1659 | }, 1660 | "concat-map": { 1661 | "version": "0.0.1", 1662 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1663 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 1664 | }, 1665 | "convert-source-map": { 1666 | "version": "1.8.0", 1667 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", 1668 | "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", 1669 | "dev": true, 1670 | "peer": true, 1671 | "requires": { 1672 | "safe-buffer": "~5.1.1" 1673 | }, 1674 | "dependencies": { 1675 | "safe-buffer": { 1676 | "version": "5.1.2", 1677 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1678 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1679 | "dev": true, 1680 | "peer": true 1681 | } 1682 | } 1683 | }, 1684 | "debug": { 1685 | "version": "4.3.3", 1686 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1687 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1688 | "dev": true, 1689 | "peer": true, 1690 | "requires": { 1691 | "ms": "2.1.2" 1692 | } 1693 | }, 1694 | "deepmerge": { 1695 | "version": "4.2.2", 1696 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 1697 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 1698 | "dev": true 1699 | }, 1700 | "electron-to-chromium": { 1701 | "version": "1.4.86", 1702 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz", 1703 | "integrity": "sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w==", 1704 | "dev": true, 1705 | "peer": true 1706 | }, 1707 | "escalade": { 1708 | "version": "3.1.1", 1709 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 1710 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 1711 | "dev": true, 1712 | "peer": true 1713 | }, 1714 | "escape-string-regexp": { 1715 | "version": "1.0.5", 1716 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 1717 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 1718 | "dev": true 1719 | }, 1720 | "estree-walker": { 1721 | "version": "1.0.1", 1722 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", 1723 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" 1724 | }, 1725 | "fs.realpath": { 1726 | "version": "1.0.0", 1727 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1728 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 1729 | }, 1730 | "fsevents": { 1731 | "version": "2.3.2", 1732 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1733 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1734 | "optional": true 1735 | }, 1736 | "function-bind": { 1737 | "version": "1.1.1", 1738 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1739 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 1740 | }, 1741 | "gensync": { 1742 | "version": "1.0.0-beta.2", 1743 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 1744 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 1745 | "dev": true, 1746 | "peer": true 1747 | }, 1748 | "glob": { 1749 | "version": "7.2.0", 1750 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 1751 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 1752 | "requires": { 1753 | "fs.realpath": "^1.0.0", 1754 | "inflight": "^1.0.4", 1755 | "inherits": "2", 1756 | "minimatch": "^3.0.4", 1757 | "once": "^1.3.0", 1758 | "path-is-absolute": "^1.0.0" 1759 | } 1760 | }, 1761 | "globals": { 1762 | "version": "11.12.0", 1763 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 1764 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 1765 | "dev": true, 1766 | "peer": true 1767 | }, 1768 | "has": { 1769 | "version": "1.0.3", 1770 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1771 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1772 | "requires": { 1773 | "function-bind": "^1.1.1" 1774 | } 1775 | }, 1776 | "has-flag": { 1777 | "version": "3.0.0", 1778 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1779 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 1780 | "dev": true 1781 | }, 1782 | "inflight": { 1783 | "version": "1.0.6", 1784 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1785 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1786 | "requires": { 1787 | "once": "^1.3.0", 1788 | "wrappy": "1" 1789 | } 1790 | }, 1791 | "inherits": { 1792 | "version": "2.0.4", 1793 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1794 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1795 | }, 1796 | "is-core-module": { 1797 | "version": "2.8.1", 1798 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", 1799 | "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", 1800 | "requires": { 1801 | "has": "^1.0.3" 1802 | } 1803 | }, 1804 | "is-module": { 1805 | "version": "1.0.0", 1806 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 1807 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 1808 | "dev": true 1809 | }, 1810 | "is-reference": { 1811 | "version": "1.2.1", 1812 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", 1813 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", 1814 | "requires": { 1815 | "@types/estree": "*" 1816 | } 1817 | }, 1818 | "jest-worker": { 1819 | "version": "26.6.2", 1820 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", 1821 | "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", 1822 | "dev": true, 1823 | "requires": { 1824 | "@types/node": "*", 1825 | "merge-stream": "^2.0.0", 1826 | "supports-color": "^7.0.0" 1827 | }, 1828 | "dependencies": { 1829 | "has-flag": { 1830 | "version": "4.0.0", 1831 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1832 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1833 | "dev": true 1834 | }, 1835 | "supports-color": { 1836 | "version": "7.2.0", 1837 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1838 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1839 | "dev": true, 1840 | "requires": { 1841 | "has-flag": "^4.0.0" 1842 | } 1843 | } 1844 | } 1845 | }, 1846 | "js-tokens": { 1847 | "version": "4.0.0", 1848 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1849 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1850 | "dev": true 1851 | }, 1852 | "jsesc": { 1853 | "version": "2.5.2", 1854 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 1855 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 1856 | "dev": true, 1857 | "peer": true 1858 | }, 1859 | "json5": { 1860 | "version": "2.2.0", 1861 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", 1862 | "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", 1863 | "dev": true, 1864 | "peer": true, 1865 | "requires": { 1866 | "minimist": "^1.2.5" 1867 | } 1868 | }, 1869 | "magic-string": { 1870 | "version": "0.25.9", 1871 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", 1872 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", 1873 | "requires": { 1874 | "sourcemap-codec": "^1.4.8" 1875 | } 1876 | }, 1877 | "merge-stream": { 1878 | "version": "2.0.0", 1879 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 1880 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 1881 | "dev": true 1882 | }, 1883 | "minimatch": { 1884 | "version": "3.1.2", 1885 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1886 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1887 | "requires": { 1888 | "brace-expansion": "^1.1.7" 1889 | } 1890 | }, 1891 | "minimist": { 1892 | "version": "1.2.5", 1893 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1894 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 1895 | "dev": true, 1896 | "peer": true 1897 | }, 1898 | "ms": { 1899 | "version": "2.1.2", 1900 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1901 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1902 | "dev": true, 1903 | "peer": true 1904 | }, 1905 | "node-releases": { 1906 | "version": "2.0.2", 1907 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", 1908 | "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", 1909 | "dev": true, 1910 | "peer": true 1911 | }, 1912 | "once": { 1913 | "version": "1.4.0", 1914 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1915 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1916 | "requires": { 1917 | "wrappy": "1" 1918 | } 1919 | }, 1920 | "path-is-absolute": { 1921 | "version": "1.0.1", 1922 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1923 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1924 | }, 1925 | "path-parse": { 1926 | "version": "1.0.7", 1927 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1928 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 1929 | }, 1930 | "picocolors": { 1931 | "version": "1.0.0", 1932 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1933 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1934 | "dev": true, 1935 | "peer": true 1936 | }, 1937 | "picomatch": { 1938 | "version": "2.3.1", 1939 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1940 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" 1941 | }, 1942 | "randombytes": { 1943 | "version": "2.1.0", 1944 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1945 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1946 | "dev": true, 1947 | "requires": { 1948 | "safe-buffer": "^5.1.0" 1949 | } 1950 | }, 1951 | "resolve": { 1952 | "version": "1.22.0", 1953 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", 1954 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", 1955 | "requires": { 1956 | "is-core-module": "^2.8.1", 1957 | "path-parse": "^1.0.7", 1958 | "supports-preserve-symlinks-flag": "^1.0.0" 1959 | } 1960 | }, 1961 | "rollup": { 1962 | "version": "2.70.1", 1963 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz", 1964 | "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==", 1965 | "requires": { 1966 | "fsevents": "~2.3.2" 1967 | } 1968 | }, 1969 | "rollup-plugin-inject": { 1970 | "version": "3.0.2", 1971 | "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", 1972 | "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", 1973 | "dev": true, 1974 | "requires": { 1975 | "estree-walker": "^0.6.1", 1976 | "magic-string": "^0.25.3", 1977 | "rollup-pluginutils": "^2.8.1" 1978 | }, 1979 | "dependencies": { 1980 | "estree-walker": { 1981 | "version": "0.6.1", 1982 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 1983 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 1984 | "dev": true 1985 | } 1986 | } 1987 | }, 1988 | "rollup-plugin-node-polyfills": { 1989 | "version": "0.2.1", 1990 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", 1991 | "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", 1992 | "dev": true, 1993 | "requires": { 1994 | "rollup-plugin-inject": "^3.0.0" 1995 | } 1996 | }, 1997 | "rollup-plugin-terser": { 1998 | "version": "7.0.2", 1999 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", 2000 | "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", 2001 | "dev": true, 2002 | "requires": { 2003 | "@babel/code-frame": "^7.10.4", 2004 | "jest-worker": "^26.2.1", 2005 | "serialize-javascript": "^4.0.0", 2006 | "terser": "^5.0.0" 2007 | } 2008 | }, 2009 | "rollup-pluginutils": { 2010 | "version": "2.8.2", 2011 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 2012 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 2013 | "dev": true, 2014 | "requires": { 2015 | "estree-walker": "^0.6.1" 2016 | }, 2017 | "dependencies": { 2018 | "estree-walker": { 2019 | "version": "0.6.1", 2020 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 2021 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 2022 | "dev": true 2023 | } 2024 | } 2025 | }, 2026 | "safe-buffer": { 2027 | "version": "5.2.1", 2028 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2029 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 2030 | "dev": true 2031 | }, 2032 | "semver": { 2033 | "version": "6.3.0", 2034 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 2035 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 2036 | "dev": true, 2037 | "peer": true 2038 | }, 2039 | "serialize-javascript": { 2040 | "version": "4.0.0", 2041 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", 2042 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", 2043 | "dev": true, 2044 | "requires": { 2045 | "randombytes": "^2.1.0" 2046 | } 2047 | }, 2048 | "source-map": { 2049 | "version": "0.7.3", 2050 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 2051 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 2052 | "dev": true 2053 | }, 2054 | "source-map-support": { 2055 | "version": "0.5.21", 2056 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 2057 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 2058 | "dev": true, 2059 | "requires": { 2060 | "buffer-from": "^1.0.0", 2061 | "source-map": "^0.6.0" 2062 | }, 2063 | "dependencies": { 2064 | "source-map": { 2065 | "version": "0.6.1", 2066 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 2067 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 2068 | "dev": true 2069 | } 2070 | } 2071 | }, 2072 | "sourcemap-codec": { 2073 | "version": "1.4.8", 2074 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 2075 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" 2076 | }, 2077 | "supports-color": { 2078 | "version": "5.5.0", 2079 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 2080 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 2081 | "dev": true, 2082 | "requires": { 2083 | "has-flag": "^3.0.0" 2084 | } 2085 | }, 2086 | "supports-preserve-symlinks-flag": { 2087 | "version": "1.0.0", 2088 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2089 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" 2090 | }, 2091 | "terser": { 2092 | "version": "5.12.1", 2093 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", 2094 | "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", 2095 | "dev": true, 2096 | "requires": { 2097 | "acorn": "^8.5.0", 2098 | "commander": "^2.20.0", 2099 | "source-map": "~0.7.2", 2100 | "source-map-support": "~0.5.20" 2101 | } 2102 | }, 2103 | "to-fast-properties": { 2104 | "version": "2.0.0", 2105 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 2106 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 2107 | "dev": true 2108 | }, 2109 | "wrappy": { 2110 | "version": "1.0.2", 2111 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2112 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 2113 | } 2114 | } 2115 | } 2116 | -------------------------------------------------------------------------------- /discodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disco-db", 3 | "version": "1.0.3", 4 | "description": "A minimalist IndexedDB wrapper and syncing solution for when your application disco(nnects) from the network", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/oslabs-beta/DiscoDB.git" 12 | }, 13 | "keywords": [ 14 | "indexeddb", 15 | "service", 16 | "worker", 17 | "offline", 18 | "synchronization" 19 | ], 20 | "author": "", 21 | "license": "MIT", 22 | "contributors": [ 23 | { 24 | "name": "Young Min Lee", 25 | "url": "https://github.com/youngmineeh" 26 | }, 27 | { 28 | "name": "Jackson Tong", 29 | "url": "https://github.com/jacksonktong" 30 | }, 31 | { 32 | "name": "Eric Gomez", 33 | "url": "https://github.com/ergomez0201" 34 | }, 35 | { 36 | "name": "Eric McCorkle", 37 | "url": "https://github.com/ericmccorkle" 38 | } 39 | ], 40 | "bugs": { 41 | "url": "https://github.com/oslabs-beta/DiscoDB/issues" 42 | }, 43 | "homepage": "https://discodb.dev/" 44 | } 45 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DiscoDB", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | {} 2 | --------------------------------------------------------------------------------