├── .env ├── public ├── ads.txt ├── favicon.ico ├── robots.txt ├── images │ ├── demo.gif │ ├── cloud │ │ ├── pr.png │ │ ├── role.png │ │ ├── actions.png │ │ ├── domains.png │ │ ├── invite.png │ │ ├── portal.gif │ │ ├── update.png │ │ ├── vscode.gif │ │ ├── function.png │ │ ├── functions.png │ │ └── environments.png │ ├── screen.png │ ├── social.png │ ├── icons │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png │ ├── screenshots │ │ ├── wide-1.png │ │ ├── wide-2.png │ │ ├── narrow-1.png │ │ └── narrow-2.png │ └── shortcuts │ │ └── upload.png ├── samples │ └── bpmtechno-120.mp3 ├── assetlinks.json ├── 400.html ├── 404.html ├── index.html ├── manifest.webmanifest └── privacy.html ├── api ├── feedback │ ├── sample.dat │ ├── function.json │ └── index.js ├── .funcignore ├── proxies.json ├── package.json ├── host.json ├── .gitignore └── package-lock.json ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .well-known └── web-app-origin-association ├── src ├── custom-hint.css ├── Admin.js ├── reportWebVitals.js ├── Home.css ├── index.js ├── App.css ├── Login.js ├── telemetry-provider.jsx ├── staticwebapp.config.json ├── TelemetryService.js ├── Account.js ├── index.css ├── Feedback.js ├── AdLink.js ├── Upload.js ├── sw │ └── service-worker.js ├── App.js ├── Home.js └── About.js ├── .gitignore ├── swa-cli.config.json ├── rollup.config.js ├── sw-build.js ├── LICENSE ├── .github └── workflows │ └── azure-static-web-apps-mango-mud-0136f961e.yml ├── package.json └── README.md /.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP = false -------------------------------------------------------------------------------- /public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-6118980043742623, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /api/feedback/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "bpm": "120", 3 | "isCorrect": true 4 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/demo.gif -------------------------------------------------------------------------------- /api/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /api/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /public/images/cloud/pr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/pr.png -------------------------------------------------------------------------------- /public/images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/screen.png -------------------------------------------------------------------------------- /public/images/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/social.png -------------------------------------------------------------------------------- /public/images/cloud/role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/role.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/images/cloud/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/actions.png -------------------------------------------------------------------------------- /public/images/cloud/domains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/domains.png -------------------------------------------------------------------------------- /public/images/cloud/invite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/invite.png -------------------------------------------------------------------------------- /public/images/cloud/portal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/portal.gif -------------------------------------------------------------------------------- /public/images/cloud/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/update.png -------------------------------------------------------------------------------- /public/images/cloud/vscode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/vscode.gif -------------------------------------------------------------------------------- /public/images/cloud/function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/function.png -------------------------------------------------------------------------------- /public/images/cloud/functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/functions.png -------------------------------------------------------------------------------- /public/samples/bpmtechno-120.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/samples/bpmtechno-120.mp3 -------------------------------------------------------------------------------- /public/images/cloud/environments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/cloud/environments.png -------------------------------------------------------------------------------- /public/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /public/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/images/screenshots/wide-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/screenshots/wide-1.png -------------------------------------------------------------------------------- /public/images/screenshots/wide-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/screenshots/wide-2.png -------------------------------------------------------------------------------- /public/images/shortcuts/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/shortcuts/upload.png -------------------------------------------------------------------------------- /public/images/screenshots/narrow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/screenshots/narrow-1.png -------------------------------------------------------------------------------- /public/images/screenshots/narrow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webmaxru/bpm-counter/HEAD/public/images/screenshots/narrow-2.png -------------------------------------------------------------------------------- /.well-known/web-app-origin-association: -------------------------------------------------------------------------------- 1 | { 2 | "web_apps": [ 3 | { 4 | "web_app_identity": "https://bpm-in.push.foo" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "func start", 7 | "test": "echo \"No tests yet...\"" 8 | }, 9 | "dependencies": { 10 | "applicationinsights": "^2.3.5" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/custom-hint.css: -------------------------------------------------------------------------------- 1 | .custom-hint .react-hint__content { 2 | background-color: #35748c; 3 | border: 1px solid rgba(255, 255, 255, 0.3); 4 | } 5 | 6 | .custom-hint:after { 7 | border-bottom-color: rgba(255, 255, 255, 0.3); 8 | border-top-color: rgba(255, 255, 255, 0.3); 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Node Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229, 9 | "preLaunchTask": "func: host start" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/assetlinks.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "relation": ["delegate_permission/common.handle_all_urls"], 3 | "target" : { "namespace": "android_app", "package_name": "no.bpmtech.twa", 4 | "sha256_cert_fingerprints": ["EB:4A:CA:4B:33:AB:06:BE:1E:8A:D0:71:B3:96:94:DB:AB:41:83:BE:E3:9F:0D:5B:11:4A:86:82:4F:0E:74:84"] } 5 | } 6 | ] -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": "api", 3 | "azureFunctions.postDeployTask": "npm install (functions)", 4 | "azureFunctions.projectLanguage": "JavaScript", 5 | "azureFunctions.projectRuntime": "~3", 6 | "debug.internalConsoleOptions": "neverOpen", 7 | "azureFunctions.preDeployTask": "npm prune (functions)" 8 | } -------------------------------------------------------------------------------- /api/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensionBundle": { 12 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 13 | "version": "[2.*, 3.0.0)" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Admin.js: -------------------------------------------------------------------------------- 1 | function Admin() { 2 | return ( 3 |
4 |

Admin

5 | 6 |
/account
7 | Account 8 | 9 |
10 |
11 | 12 |
/.auth/logout
13 | Log out 14 |
15 | ); 16 | } 17 | 18 | export default Admin; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | *.pem 26 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /swa-cli.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", 3 | "configurations": { 4 | "bpm-counter": { 5 | "appLocation": ".", 6 | "apiLocation": "api", 7 | "outputLocation": "build", 8 | "appBuildCommand": "npm run build", 9 | "apiBuildCommand": "npm run build --if-present", 10 | "run": "npm start", 11 | "appDevserverUrl": "http://localhost:3000" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import replace from 'rollup-plugin-replace' 3 | import { terser } from 'rollup-plugin-terser' 4 | 5 | export default { 6 | input: 'build/sw.js', 7 | output: { 8 | file: 'build/sw.js', 9 | format: 'iife' 10 | }, 11 | plugins: [ 12 | resolve(), 13 | replace({ 14 | 'process.env.NODE_ENV': JSON.stringify('production') 15 | }), 16 | terser() 17 | ] 18 | } -------------------------------------------------------------------------------- /api/feedback/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": [ 9 | "post" 10 | ] 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | }, 17 | { 18 | "type": "cosmosDB", 19 | "direction": "out", 20 | "name": "outputDocument", 21 | "databaseName": "bpmtech-db", 22 | "collectionName": "feedback", 23 | "createIfNotExists": true, 24 | "connectionStringSetting": "bpmcounterdbaccount_DOCUMENTDB" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/Home.css: -------------------------------------------------------------------------------- 1 | .content { 2 | text-align: center; 3 | } 4 | 5 | .btn-start { 6 | background: #f2b680; 7 | border-radius: 5px; 8 | color: #ffffff; 9 | border: 0; 10 | padding: 0.5em 1em; 11 | font-size: 2em; 12 | } 13 | 14 | .btn-stop { 15 | background: #35748c; 16 | border-radius: 5px; 17 | color: #ffffff; 18 | border: 0; 19 | padding: 0.5em 1em; 20 | font-size: 1em; 21 | margin-top: 2em; 22 | } 23 | 24 | @media (min-width: 768px) { 25 | .btn-start { 26 | padding: 1em 2em; 27 | } 28 | 29 | .content { 30 | flex: 1; 31 | padding: 0 4em; 32 | margin: 0; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "func", 6 | "command": "host start", 7 | "problemMatcher": "$func-node-watch", 8 | "isBackground": true, 9 | "dependsOn": "npm install (functions)", 10 | "options": { 11 | "cwd": "${workspaceFolder}/api" 12 | } 13 | }, 14 | { 15 | "type": "shell", 16 | "label": "npm install (functions)", 17 | "command": "npm install", 18 | "options": { 19 | "cwd": "${workspaceFolder}/api" 20 | } 21 | }, 22 | { 23 | "type": "shell", 24 | "label": "npm prune (functions)", 25 | "command": "npm prune --production", 26 | "problemMatcher": [], 27 | "options": { 28 | "cwd": "${workspaceFolder}/api" 29 | } 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /sw-build.js: -------------------------------------------------------------------------------- 1 | const { injectManifest } = require("workbox-build"); 2 | 3 | let workboxConfig = { 4 | globDirectory: "build", 5 | globPatterns: ["favicon.ico", "index.html", "privacy.html", "static/**/*", "images/icons/*","manifest.webmanifest"], 6 | globIgnores: [ 7 | "**/*.map", 8 | "**/*.txt" 9 | ], 10 | 11 | swSrc: "src/sw/service-worker.js", 12 | swDest: "build/sw.js", 13 | 14 | // React takes care of cache busting for JS and CSS (in prod mode) 15 | dontCacheBustURLsMatching: new RegExp(".+.chunk.(?:js|css)"), 16 | 17 | // By default, Workbox will not cache files larger than 2Mb (might be an issue for dev builds) 18 | maximumFileSizeToCacheInBytes: 4 * 1024 * 1024, // 4Mb 19 | }; 20 | 21 | injectManifest(workboxConfig).then(({ count, size }) => { 22 | console.log( 23 | `Generated ${workboxConfig.swDest}, which will precache ${count} files, totaling ${size} bytes.` 24 | ); 25 | }); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import ReactGA from 'react-ga4'; 7 | 8 | ReactGA.initialize('G-PKNGLJ5FKX'); 9 | ReactGA.send('pageview'); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | // If you want to start measuring performance in your app, pass a function 19 | // to log results (for example: reportWebVitals(console.log)) 20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 21 | reportWebVitals(); 22 | 23 | /* 24 | document.addEventListener('DOMContentLoaded', function () { 25 | if (!checkPerformanceCookiesEnabled()) { 26 | if (typeof appInsights === 'object' && typeof appInsights.config === 'object') { 27 | window.appInsights.config.disableCookiesUsage = true; 28 | } 29 | } 30 | }); 31 | */ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Maxim Salnikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .body { 2 | display: flex; 3 | flex: 1 0 auto; /* 2 */ 4 | flex-direction: column; 5 | padding: 2em; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | 10 | .nav { 11 | order: -1; 12 | } 13 | 14 | .nav, 15 | .ads { 16 | padding: 1em; 17 | color: #0d4c73; 18 | } 19 | 20 | header { 21 | color: #ffffff; 22 | text-align: center; 23 | display: flex; 24 | } 25 | 26 | footer p { 27 | font-size: 0.75em; 28 | background-color: #35748c; 29 | color: #ffffff; 30 | padding: 0.25em 0.5em; 31 | text-align: center; 32 | margin: 0; 33 | } 34 | 35 | @media (min-width: 768px) { 36 | .body { 37 | flex-direction: row; 38 | } 39 | 40 | .nav, 41 | .ads { 42 | flex: 0 0 12em; 43 | } 44 | } 45 | 46 | header h1 { 47 | flex: 1; 48 | text-align: center; 49 | margin-bottom: 0; 50 | } 51 | 52 | header h1 a { 53 | text-decoration: none; 54 | } 55 | 56 | .about { 57 | margin-left: auto; 58 | background: #35748c; 59 | border-radius: 0 0 0 2em; 60 | color: #ffffff; 61 | border: 0; 62 | padding: 0.7em 0.6em 0 1em; 63 | font-size: 1em; 64 | line-height: 0.5em; 65 | font-weight: bold; 66 | text-decoration: none; 67 | } 68 | -------------------------------------------------------------------------------- /src/Login.js: -------------------------------------------------------------------------------- 1 | function Login() { 2 | return ( 3 |
4 |

Log in

5 | 6 |
/.auth/login/twitter
7 | 8 | Log in with Twitter 9 | 10 |
11 |
12 |
/.auth/login/github
13 | 14 | Log in with GitHub 15 | 16 |
17 |
18 |
/.auth/login/aad
19 | 20 | Log in with Azure Active Directory 21 |
22 | (disabled for the demo purposes) 23 |
24 |
25 |
/.auth/login/twitter?post_login_redirect_uri=/account
26 | 27 | Log in with Twitter with redirect to /account 28 | 29 | 30 |
/.auth/login/github?post_login_redirect_uri=/account
31 | 32 | Log in with GitHub with redirect to /admin 33 | 34 |
35 | ); 36 | } 37 | 38 | export default Login; 39 | -------------------------------------------------------------------------------- /src/telemetry-provider.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { withAITracking } from '@microsoft/applicationinsights-react-js'; 3 | import { ai } from './TelemetryService'; 4 | import { withRouter } from 'react-router-dom'; 5 | 6 | /** 7 | * This Component provides telemetry with Azure App Insights 8 | * 9 | * NOTE: the package '@microsoft/applicationinsights-react-js' has a HOC withAITracking that requires this to be a Class Component rather than a Functional Component 10 | */ 11 | class TelemetryProvider extends Component { 12 | state = { 13 | initialized: false, 14 | }; 15 | 16 | componentDidMount() { 17 | const { history } = this.props; 18 | const { initialized } = this.state; 19 | const AppInsightsConnectionString= this.props.connectionString; // PUT YOUR KEY HERE 20 | if ( 21 | !Boolean(initialized) && 22 | Boolean(AppInsightsConnectionString) && 23 | Boolean(history) 24 | ) { 25 | ai.initialize(AppInsightsConnectionString, history); 26 | this.setState({ initialized: true }); 27 | } 28 | 29 | this.props.after(); 30 | } 31 | 32 | render() { 33 | const { children } = this.props; 34 | return {children}; 35 | } 36 | } 37 | 38 | export default withRouter(withAITracking(ai.reactPlugin, TelemetryProvider)); 39 | -------------------------------------------------------------------------------- /src/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "route": "/account", 5 | "allowedRoles": ["authenticated"] 6 | }, 7 | { 8 | "route": "/admin/*", 9 | "allowedRoles": ["administrator"] 10 | }, 11 | { 12 | "route": "/images/*", 13 | "headers": { 14 | "cache-control": "must-revalidate, max-age=15770000" 15 | } 16 | }, 17 | { 18 | "route": "/login-account", 19 | "rewrite": "/.auth/login/twitter?post_login_redirect_uri=/account" 20 | }, 21 | { 22 | "route": "/login-twitter", 23 | "rewrite": "/.auth/login/twitter" 24 | }, 25 | { 26 | "route": "/.auth/login/aad", 27 | "statusCode": 404 28 | }, 29 | { 30 | "route": "/logout", 31 | "redirect": "/.auth/logout?post_logout_redirect_uri=/about" 32 | }, 33 | { 34 | "route": "/aboutme", 35 | "redirect": "/about", 36 | "statusCode": 301 37 | } 38 | ], 39 | "navigationFallback": { 40 | "rewrite": "index.html", 41 | "exclude": ["/images/*.{png,jpg,gif}", "/static/*"] 42 | }, 43 | "responseOverrides": { 44 | "400": { 45 | "rewrite": "/400.html" 46 | }, 47 | "401": { 48 | "redirect": "/login", 49 | "statusCode": 302 50 | }, 51 | "403": { 52 | "rewrite": "/400.html" 53 | }, 54 | "404": { 55 | "rewrite": "/404.html" 56 | } 57 | }, 58 | "globalHeaders": { 59 | "X-Powered-By": "Maxim Salnikov and Azure Static Web Apps" 60 | }, 61 | "mimeTypes": { 62 | ".json": "text/json", 63 | ".webmanifest": "application/manifest+json" 64 | } 65 | } -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | # TypeScript output 87 | dist 88 | out 89 | 90 | # Azure Functions artifacts 91 | bin 92 | obj 93 | appsettings.json 94 | local.settings.json -------------------------------------------------------------------------------- /api/feedback/index.js: -------------------------------------------------------------------------------- 1 | const appInsights = require('applicationinsights'); 2 | appInsights.setup(); 3 | const client = appInsights.defaultClient; 4 | 5 | module.exports = async function (context, req) { 6 | var operationIdOverride = { 7 | 'ai.operation.id': context.traceContext.traceparent, 8 | }; 9 | 10 | if (!req.body || !('bpm' in req.body) || !('isCorrect' in req.body)) { 11 | client.trackException({ 12 | exception: new Error('No required parameter!'), 13 | tagOverrides: operationIdOverride, 14 | }); 15 | 16 | context.res = { status: 404, body: 'No required parameter!' }; 17 | context.done(); 18 | } 19 | 20 | let clientPrincipal = {}; 21 | 22 | try { 23 | const header = req.headers['x-ms-client-principal']; 24 | const encoded = Buffer.from(header, 'base64'); 25 | const decoded = encoded.toString('ascii'); 26 | clientPrincipal = JSON.parse(decoded); 27 | } catch (err) { 28 | context.log(`${err.name}: ${err.message}`); 29 | } 30 | 31 | const bpm = req.body.bpm; 32 | const isCorrect = req.body.isCorrect; 33 | const timestamp = Math.floor(Date.now() / 1); 34 | 35 | context.bindings.outputDocument = JSON.stringify({ 36 | id: new Date().toISOString() + Math.random().toString().substr(2, 8), 37 | bpm: bpm, 38 | isCorrect: isCorrect, 39 | timestamp: timestamp, 40 | }); 41 | 42 | client.trackEvent({ 43 | name: 'feedback_save', 44 | tagOverrides: operationIdOverride, 45 | properties: { 46 | id: new Date().toISOString() + Math.random().toString().substr(2, 8), 47 | bpm: bpm, 48 | isCorrect: isCorrect, 49 | timestamp: timestamp, 50 | }, 51 | }); 52 | 53 | context.res = { 54 | body: { 55 | message: 'Thank you!', 56 | clientPrincipal: clientPrincipal, 57 | }, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /.github/workflows/azure-static-web-apps-mango-mud-0136f961e.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened, closed] 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build_and_deploy_job: 14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') 15 | runs-on: ubuntu-latest 16 | name: Build and Deploy Job 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | - name: Build And Deploy 22 | id: builddeploy 23 | uses: Azure/static-web-apps-deploy@v1 24 | with: 25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_MUD_0136F961E }} 26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 27 | action: "upload" 28 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### 29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 30 | app_location: "/" # App source code path 31 | api_location: "api" # Api source code path - optional 32 | output_location: "build" # Built app content directory - optional 33 | ###### End of Repository/Build Configurations ###### 34 | 35 | close_pull_request_job: 36 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 37 | runs-on: ubuntu-latest 38 | name: Close Pull Request Job 39 | steps: 40 | - name: Close Pull Request 41 | id: closepullrequest 42 | uses: Azure/static-web-apps-deploy@v1 43 | with: 44 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_MUD_0136F961E }} 45 | action: "close" 46 | -------------------------------------------------------------------------------- /public/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | BPM Techno - Free Online Real-Time BPM Counter for DJ 12 | 13 | 14 | 18 | 19 | 67 | 68 |
69 |

💿

70 |

400 Not your beat

71 |

Go to homepage

72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | BPM Techno - Free Online Real-Time BPM Counter for DJ 12 | 13 | 14 | 18 | 19 | 67 | 68 |
69 |

💿

70 |

404 Beat not found

71 |

Go to homepage

72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /src/TelemetryService.js: -------------------------------------------------------------------------------- 1 | import { ApplicationInsights } from '@microsoft/applicationinsights-web'; 2 | import { ReactPlugin } from '@microsoft/applicationinsights-react-js'; 3 | import { ClickAnalyticsPlugin } from '@microsoft/applicationinsights-clickanalytics-js'; 4 | 5 | let reactPlugin = null; 6 | let clickPlugin = null; 7 | let appInsights = null; 8 | 9 | /** 10 | * Create the App Insights Telemetry Service 11 | * @return {{reactPlugin: ReactPlugin, appInsights: Object, initialize: Function}} - Object 12 | */ 13 | const createTelemetryService = () => { 14 | /** 15 | * Initialize the Application Insights class 16 | * @param {string} connectionString - Application Insights Instrumentation Key 17 | * @param {Object} browserHistory - client's browser history, supplied by the withRouter HOC 18 | * @return {void} 19 | */ 20 | const initialize = (connectionString, browserHistory) => { 21 | if (!browserHistory) { 22 | throw new Error('Could not initialize Telemetry Service'); 23 | } 24 | if (!connectionString) { 25 | throw new Error( 26 | 'Instrumentation key not provided in ./src/telemetry-provider.jsx' 27 | ); 28 | } 29 | 30 | reactPlugin = new ReactPlugin(); 31 | 32 | clickPlugin = new ClickAnalyticsPlugin(); 33 | const clickPluginConfig = { 34 | autoCapture: true, 35 | }; 36 | 37 | appInsights = new ApplicationInsights({ 38 | config: { 39 | connectionString: connectionString, 40 | maxBatchInterval: 0, 41 | disableFetchTracking: false, 42 | extensions: [reactPlugin, clickPlugin], 43 | extensionConfig: { 44 | [reactPlugin.identifier]: { 45 | history: browserHistory, 46 | }, 47 | [clickPlugin.identifier]: clickPluginConfig, 48 | }, 49 | }, 50 | }); 51 | 52 | appInsights.loadAppInsights(); 53 | }; 54 | 55 | return { reactPlugin, appInsights, initialize }; 56 | }; 57 | 58 | export const ai = createTelemetryService(); 59 | export const getAppInsights = () => appInsights; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bpm-counter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@microsoft/applicationinsights-clickanalytics-js": "^2.8.8", 7 | "@microsoft/applicationinsights-react-js": "^3.4.0", 8 | "@microsoft/applicationinsights-web": "^2.8.8", 9 | "@testing-library/jest-dom": "^5.11.4", 10 | "@testing-library/react": "^11.1.0", 11 | "@testing-library/user-event": "^12.1.10", 12 | "audiomotion-analyzer": "^3.5.0", 13 | "bpm-detective": "^2.0.5", 14 | "loglevel": "^1.7.1", 15 | "react": "^17.0.2", 16 | "react-device-detect": "^1.17.0", 17 | "react-dom": "^17.0.2", 18 | "react-ga4": "^1.1.2", 19 | "react-hint": "^3.2.1", 20 | "react-router-dom": "^5.3.0", 21 | "react-scripts": "^5.0.1", 22 | "react-toastify": "^8.0.2", 23 | "realtime-bpm-analyzer": "^1.1.5", 24 | "rollup": "^2.56.3", 25 | "rollup-plugin-node-resolve": "^5.2.0", 26 | "rollup-plugin-replace": "^2.2.0", 27 | "rollup-plugin-terser": "^7.0.2", 28 | "web-vitals": "^1.0.1", 29 | "workbox-build": "^7.0.0" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build && npm run build-sw", 34 | "test": "react-scripts test", 35 | "eject": "react-scripts eject", 36 | "startsecure": "cross-env HTTPS=true SSL_CRT_FILE=192.168.1.9.pem SSL_KEY_FILE=192.168.1.9-key.pem react-scripts start", 37 | "startprod": "cross-env NODE_ENV=production react-scripts start", 38 | "build-sw": "node sw-build.js && npx rollup -c", 39 | "fix-ssl": "npm audit fix --force" 40 | }, 41 | "eslintConfig": { 42 | "extends": [ 43 | "react-app", 44 | "react-app/jest" 45 | ] 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | }, 59 | "devDependencies": { 60 | "cross-env": "^7.0.3" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Account.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | function Account() { 4 | const [clientPrincipal, setClientPrincipal] = useState(null); 5 | const [isLoading, setIsLoading] = useState(true); 6 | 7 | useEffect(() => { 8 | const run = async () => { 9 | try { 10 | const res = await fetch('/.auth/me'); 11 | const json = await res.json(); 12 | if (json.clientPrincipal) { 13 | setClientPrincipal(json.clientPrincipal); 14 | } 15 | } catch (e) { 16 | if (window.location.hostname === 'localhost') { 17 | console.warn( 18 | "Can't access the auth endpoint. For local development, please use the Static Web Apps CLI to emulate authentication: https://github.com/azure/static-web-apps-cli" 19 | ); 20 | } else { 21 | console.error(`Failed to unpack JSON.`, e); 22 | } 23 | } 24 | 25 | setIsLoading(false); 26 | }; 27 | 28 | run(); 29 | }, []); 30 | 31 | return ( 32 |
33 |

Account

34 | 35 | {isLoading ? ( 36 |

Loading...

37 | ) : ( 38 | <> 39 | {clientPrincipal ? ( 40 | <> 41 |
/.auth/me
42 | 48 | 49 |
50 | 51 |
/.auth/logout
52 | Log out 53 | 54 |
55 |
56 | 57 |
/.auth/purge/twitter
58 | 59 | Remove personal information for Twitter as a provider 60 | 61 | 62 |
63 |
64 | 65 |
/.auth/purge/github
66 | 67 | Remove personal information for GitHub as a provider 68 | 69 | 70 | ) : ( 71 |

Not logged in. Log in here.

72 | )} 73 | 74 | )} 75 |
76 | ); 77 | } 78 | 79 | export default Account; 80 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: -webkit-fill-available; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | font-family: 'Saira', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 8 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | 13 | min-height: 100vh; 14 | min-height: -webkit-fill-available; 15 | 16 | background-color: #0d4c73; 17 | color: #fff; 18 | 19 | display: flex; 20 | } 21 | 22 | body > div { 23 | width: 100%; 24 | display: flex; 25 | } 26 | 27 | body > div > div { 28 | width: 100%; 29 | display: flex; 30 | flex-direction: column; 31 | } 32 | 33 | a { 34 | color: #ffffff; 35 | } 36 | 37 | h1 { 38 | font-size: 1em; 39 | } 40 | 41 | h2 { 42 | font-size: 8em; 43 | color: #f2b680; 44 | margin: 0; 45 | line-height: 0.5em; 46 | } 47 | 48 | h3 { 49 | font-size: 2em; 50 | color: #d98c5f; 51 | margin-bottom: 1em; 52 | line-height: 1em; 53 | } 54 | 55 | h4 { 56 | font-size: 3em; 57 | color: #35748c; 58 | margin-bottom: 0; 59 | line-height: 0.5em; 60 | } 61 | 62 | ul, 63 | ol { 64 | text-align: left; 65 | margin-bottom: 1em; 66 | } 67 | 68 | button, 69 | .button { 70 | background: #35748c; 71 | border-radius: 5px; 72 | color: #ffffff; 73 | border: 0; 74 | padding: 0.5em; 75 | font-size: 1em; 76 | text-decoration: none; 77 | } 78 | 79 | .button { 80 | display: inline-block; 81 | } 82 | 83 | button:hover, 84 | .button:hover { 85 | box-shadow: 0 0 1em rgba(255, 255, 255, 0.2); 86 | } 87 | 88 | button:active, 89 | .button:hover { 90 | box-shadow: 0 0.2em 0.5em rgba(255, 255, 255, 0.3); 91 | } 92 | 93 | dl { 94 | text-align: left; 95 | } 96 | 97 | pre { 98 | border-radius: 5px; 99 | border: 1px dashed #35748c; 100 | padding: 0.5em 1em; 101 | margin-bottom: 1em; 102 | font-size: 1em; 103 | } 104 | 105 | input[type='text'], 106 | input[type='password'], 107 | input[type='email'], 108 | input[type='url'] { 109 | background: #ffffff; 110 | border-radius: 5px; 111 | color: #333333; 112 | border: 1px solid #35748c; 113 | padding: 0.5em; 114 | font-size: 1em; 115 | width: 100%; 116 | } 117 | 118 | .hint { 119 | border-bottom: 1px dashed #d98c5f; 120 | color: #d98c5f; 121 | text-decoration: none; 122 | cursor: pointer; 123 | } 124 | 125 | label { 126 | margin-bottom: 0.5em; 127 | display: block; 128 | } -------------------------------------------------------------------------------- /src/Feedback.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ToastContainer, toast } from 'react-toastify'; 3 | import 'react-toastify/dist/ReactToastify.css'; 4 | import ReactHintFactory from 'react-hint'; 5 | import 'react-hint/css/index.css'; 6 | import './custom-hint.css'; 7 | import ReactGA from 'react-ga4'; 8 | 9 | const ReactHint = ReactHintFactory(React); 10 | 11 | class Feedback extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.sendFeedback = this.sendFeedback.bind(this); 16 | 17 | this.url = `/api/feedback`; 18 | this.requestOptions = { 19 | method: 'POST', 20 | headers: { 'Content-Type': 'application/json' }, 21 | }; 22 | } 23 | 24 | componentDidMount() { 25 | this.instance.toggleHint({ target: this.button }); 26 | setTimeout(() => { 27 | if (this.instance) this.instance.toggleHint({ target: null }); 28 | }, 5000); 29 | } 30 | 31 | async sendFeedback(isCorrect) { 32 | this.requestOptions.body = JSON.stringify({ 33 | bpm: this.props.bpm, 34 | type: this.props.type, 35 | isCorrect: isCorrect, 36 | }); 37 | 38 | try { 39 | // Let's assume that the request is successful 40 | toast.success('Sending your feedback. Thanks!'); 41 | 42 | let response = await fetch(this.url, this.requestOptions); 43 | 44 | ReactGA.event('share', { 45 | method: 'API', 46 | content_type: 'feedback', 47 | item_id: isCorrect, 48 | }); 49 | this.props.appInsights.trackEvent({ 50 | name: 'share', 51 | properties: { 52 | method: 'API', 53 | content_type: 'feedback', 54 | item_id: isCorrect, 55 | }, 56 | }); 57 | 58 | if (!response.ok) { 59 | this.props.log.error(`HTTP error. Status: ${response.status}`); 60 | throw new Error(); 61 | } 62 | } catch (err) { 63 | toast.error('Oops, no luck with sending this time'); 64 | this.props.log.error(`${err.name}: ${err.message}`); 65 | } 66 | } 67 | 68 | render() { 69 | return ( 70 |
71 |
72 |

Does {this.props.bpm} sound correct?

73 | 80 |     81 | 82 | 83 | (this.instance = ref)} 86 | delay="2000" 87 | position="bottom" 88 | className="custom-hint react-hint" 89 | /> 90 |
91 | ); 92 | } 93 | } 94 | 95 | export default Feedback; 96 | -------------------------------------------------------------------------------- /src/AdLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactGA from 'react-ga4'; 3 | 4 | function AdLink(props) { 5 | const appInsights = props.appInsights; 6 | 7 | console.log(appInsights); 8 | let ad = props.ad; 9 | 10 | const getRandomInt = (max) => { 11 | return Math.floor(Math.random() * max) + 1; 12 | }; 13 | 14 | let ads = { 15 | 'search-dj-controllers': { 16 | link: 'https://www.amazon.com/gp/search?ie=UTF8&tag=webapplication-20&linkCode=ur2&linkId=8ca01d484b759858f609454d6dbc68b7&camp=1789&creative=9325&index=mi&keywords=dj%20controllers', 17 | texts: [ 18 | '🎧🎛 Get 💰Savings on 🔥Hot DJ Controllers!', 19 | '🤑DJ Controllers on 🔥Sale Now! 🤩', 20 | '🤑🤩Score 💰Savings on 🎧DJ Controllers!', 21 | '🤑🤩Grab 💰Savings on 🎧DJ Controllers!', 22 | '🤑🤩💰Save Now on 🎧DJ Controllers!', 23 | '🤑🤩💰Affordable 🎧DJ Controllers!', 24 | '🤑🤩💰Grab 🎧DJ Controllers at 💰Savings!', 25 | '🤑🤩💰Get 🎧DJ Controllers at 💰Savings!', 26 | '🤑🤩💰Affordable 🎧DJ Controllers Here!', 27 | '🤑🤩💰Get 🎧DJ Controllers at 💰Savings Now!', 28 | ], 29 | }, 30 | 'item-sample-pack': { 31 | link: 'https://www.amazon.com/Samples-Maschine-Ableton-Instruments-Production/dp/B09C2M5D82/ref=sr_1_1?crid=6TO3QGDAE9Z0&keywords=ableton+sound+pack&qid=1688655275&rnid=2941120011&s=musical-instruments&sprefix=ableton+sound+pack%252Caps%252C172&sr=1-1&_encoding=UTF8&tag=webapplication-20&linkCode=ur2&linkId=5a48eda719a3ed9bc124e62c1dce4080&camp=1789&creative=9325', 32 | texts: [ 33 | '🎧🤩100Gb Sound Pack: 100K+ Samples for Ableton, MPK, Logic🔥', 34 | '🎧🤩100Gb Sound Pack: 100K+ Samples | 808s, Synths, Loops, FX🔥', 35 | '🎧🤩EDM Production USB: 100K+ Samples for Ableton, MPK, Logic🔥', 36 | ], 37 | }, 38 | 'item-music-prod': { 39 | link: 'https://www.amazon.com/Music-Production-Beginners-2022-Songwriters-ebook/dp/B09SZJD7XN/ref=sr_1_7?keywords=edm+dj+music+production&qid=1688664662&rnid=2941120011&s=books&sr=1-7&_encoding=UTF8&tag=webapplication-20&linkCode=ur2&linkId=4f3bc88283a657ecc139adc47e46c2bf&camp=1789&creative=9325', 40 | texts: [ 41 | '🎧📖Music Production For Beginners 2022+ Edition🔥', 42 | '🎧📖How to Produce Music: The Easy to Read Guide for Beginners🔥', 43 | ], 44 | }, 45 | }; 46 | 47 | let adText = ads[ad].texts[getRandomInt(ads[ad].texts.length) - 1]; 48 | 49 | const handleClick = (event) => { 50 | ReactGA.event('click_ad', { 51 | ad: ad, 52 | text: adText, 53 | }); 54 | appInsights.trackEvent({ 55 | name: 'click_ad', 56 | properties: { 57 | ad: ad, 58 | text: adText, 59 | }, 60 | }); 61 | }; 62 | 63 | return ( 64 | 70 | {adText} 71 | 72 | ); 73 | } 74 | 75 | export default AdLink; 76 | -------------------------------------------------------------------------------- /src/Upload.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import detect from 'bpm-detective'; 3 | import { ToastContainer, toast } from 'react-toastify'; 4 | import 'react-toastify/dist/ReactToastify.css'; 5 | import Feedback from './Feedback.js'; 6 | import ReactGA from 'react-ga4'; 7 | 8 | function Upload(props) { 9 | let log = props.log; 10 | const appInsights = props.appInsights; 11 | 12 | const query = new URLSearchParams(window.location.search); 13 | 14 | const [url, setUrl] = useState(query.get('url') ?? ''); 15 | const [primaryBPM, setPrimaryBPM] = useState(``); 16 | const [isResultReady, setIsResultReady] = useState(false); 17 | 18 | useEffect(() => { 19 | ReactGA.event('select_content', { 20 | content_type: 'mode', 21 | item_id: 'url', 22 | }); 23 | }, []); 24 | 25 | const calculateBPM = () => { 26 | setIsResultReady(false); 27 | window.AudioContext = window.AudioContext || window.webkitAudioContext; 28 | const context = new window.AudioContext(); 29 | 30 | fetch(url) 31 | .then(async (response) => { 32 | const buffer = await response.arrayBuffer(); 33 | 34 | const data = await new Promise((resolve, reject) => { 35 | context.decodeAudioData(buffer, resolve, reject); 36 | }); 37 | 38 | const bpm = detect(data); 39 | setPrimaryBPM(bpm); 40 | setIsResultReady(true); 41 | 42 | ReactGA.event('detect', { 43 | mode: 'url', 44 | bpm: bpm, 45 | threshold: null, 46 | }); 47 | 48 | appInsights.trackEvent({ 49 | name: 'detect', 50 | properties: { 51 | content_type: 'mode', 52 | item_id: 'url', 53 | }, 54 | }); 55 | }) 56 | .catch((err) => { 57 | toast.error(`${err}`); 58 | console.error(err); 59 | }); 60 | }; 61 | 62 | return ( 63 |
64 | {isResultReady && primaryBPM ? ( 65 | <> 66 |

{primaryBPM}

67 |

BPM

68 | 69 | ) : null} 70 | 71 | {primaryBPM ? ( 72 | 73 | ) : null} 74 | 75 |
76 | 77 | 86 | setUrl(e.target.value)} 91 | value={url} 92 | /> 93 |
94 |
95 | 98 | 99 |
100 |
101 | 102 |

103 | Return to real-time BPM detection 104 |

105 | 106 | 107 |
108 | ); 109 | } 110 | 111 | export default Upload; 112 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BPM Techno - Free Online Real-Time BPM Counter for DJ 8 | 12 | 13 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "BPM Techno", 3 | "name": "BPM Techno - Real-Time BPM Counter", 4 | "description": "Produce some beats using any player, launchpad, keyboard, and mix them live with another track easily and precisely using this free online real-time BPM counter", 5 | "start_url": "/?utm_source=homescreen", 6 | "display": "standalone", 7 | "display_override": ["window-control-overlay", "minimal-ui"], 8 | "theme_color": "#0d4c73", 9 | "background_color": "#0d4c73", 10 | "orientation": "portrait", 11 | "scope": "/", 12 | "categories": ["entertainment", "music"], 13 | "icons": [ 14 | { 15 | "src": "/images/icons/icon-192x192.png", 16 | "sizes": "192x192", 17 | "type": "image/png" 18 | }, 19 | { 20 | "src": "/images/icons/icon-256x256.png", 21 | "sizes": "256x256", 22 | "type": "image/png" 23 | }, 24 | { 25 | "src": "/images/icons/icon-384x384.png", 26 | "sizes": "384x384", 27 | "type": "image/png" 28 | }, 29 | { 30 | "src": "/images/icons/icon-512x512.png", 31 | "sizes": "512x512", 32 | "type": "image/png" 33 | } 34 | ], 35 | "shortcuts": [ 36 | { 37 | "name": "Upload Audio File", 38 | "short_name": "Upload File", 39 | "description": "Count BPM of the uploaded file", 40 | "url": "/upload?utm_source=homescreen", 41 | "icons": [ 42 | { 43 | "src": "/images/shortcuts/upload.png", 44 | "sizes": "192x192", 45 | "type": "image/png", 46 | "label": "Upload" 47 | } 48 | ] 49 | } 50 | ], 51 | "screenshots": [ 52 | { 53 | "src": "/images/screenshots/narrow-1.png", 54 | "sizes": "1439x2881", 55 | "platform": "narrow", 56 | "label": "BPM Techno - Free Online Real-Time BPM Counter for DJ", 57 | "type": "image/png" 58 | }, 59 | { 60 | "src": "/images/screenshots/narrow-2.png", 61 | "sizes": "1439x2881", 62 | "platform": "narrow", 63 | "label": "BPM Techno - Free Online Real-Time BPM Counter for DJ", 64 | "type": "image/png" 65 | }, 66 | { 67 | "src": "/images/screenshots/wide-1.png", 68 | "sizes": "2732x2048", 69 | "platform": "wide", 70 | "label": "BPM Techno - Free Online Real-Time BPM Counter for DJ", 71 | "type": "image/png" 72 | }, 73 | { 74 | "src": "/images/screenshots/wide-2.png", 75 | "sizes": "2732x2048", 76 | "platform": "wide", 77 | "label": "BPM Techno - Free Online Real-Time BPM Counter for DJ", 78 | "type": "image/png" 79 | } 80 | ], 81 | "launch_handler": { 82 | "route_to": "existing-client", 83 | "client_mode": "navigate-new" 84 | }, 85 | "scope_extensions": [ 86 | { 87 | "origin": "https://bpm-out.push.foo" 88 | } 89 | ], 90 | "protocol_handlers": [ 91 | { 92 | "protocol": "web+bpm", 93 | "url": "?utm_source=protocol&bpm=%s" 94 | } 95 | ], 96 | "file_handlers": [ 97 | { 98 | "action": "/upload", 99 | "accept": { 100 | "audio/mpeg": [".mp3"] 101 | }, 102 | "icons": [ 103 | { 104 | "src": "/images/shortcuts/upload.png", 105 | "sizes": "192x192", 106 | "type": "image/png", 107 | "label": "Upload" 108 | } 109 | ] 110 | } 111 | ], 112 | "share_target": { 113 | "action": "/upload", 114 | "method": "GET", 115 | "params": { 116 | "title": "title", 117 | "text": "text", 118 | "url": "url" 119 | } 120 | }, 121 | "iarc_rating_id": "09b8e729-04e0-43b1-8cb2-5caa729418a2", 122 | "related_applications": [ 123 | { 124 | "platform": "play", 125 | "url": "https://play.google.com/store/apps/details?id=no.bpmtech.twa", 126 | "id": "no.bpmtech.twa" 127 | }, { 128 | "platform": "windows", 129 | "url": "https://www.microsoft.com/store/apps/9NQ4M5SC9PQ9", 130 | "id": "9NQ4M5SC9PQ9" 131 | } 132 | ], 133 | "prefer_related_applications": false 134 | } 135 | -------------------------------------------------------------------------------- /src/sw/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /* eslint-disable no-restricted-globals */ 3 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; 4 | import { NavigationRoute, registerRoute } from 'workbox-routing'; 5 | import { setCacheNameDetails, clientsClaim } from 'workbox-core'; 6 | import { NetworkFirst } from 'workbox-strategies'; 7 | import { googleFontsCache } from 'workbox-recipes'; 8 | import { BackgroundSyncPlugin } from 'workbox-background-sync'; 9 | import * as googleAnalytics from 'workbox-google-analytics'; 10 | 11 | async function messageClient(event, messageType) { 12 | if (!event.clientId) return; 13 | 14 | // Get the client. 15 | const client = await clients.get(event.clientId); 16 | // Exit early if we don't get the client. 17 | // Eg, if it closed. 18 | if (!client) return; 19 | 20 | // Send a message to the client. 21 | client.postMessage({ 22 | type: messageType, 23 | }); 24 | } 25 | 26 | // SETTINGS 27 | 28 | // Claiming control to start runtime caching asap 29 | clientsClaim(); 30 | 31 | // Use to update the app after user triggered refresh 32 | //self.skipWaiting(); 33 | 34 | // Setting custom cache names 35 | setCacheNameDetails({ precache: 'wb6-precache', runtime: 'wb6-runtime' }); 36 | 37 | // STATIC ROUTING 38 | 39 | self.addEventListener('install', async (event) => { 40 | 41 | if (event.addRoutes) { 42 | try { 43 | event.addRoutes([ 44 | { 45 | condition: { 46 | urlPattern: { pathname: '/login' }, 47 | }, 48 | source: 'network', 49 | }, 50 | { 51 | condition: { 52 | urlPattern: { pathname: '/about' }, 53 | }, 54 | source: 'fetch-event', 55 | }, 56 | { 57 | condition: { 58 | urlPattern: { pathname: '/privacy.html' }, 59 | }, 60 | source: { 61 | cacheName: 'wb6-precache', 62 | }, 63 | }, 64 | { 65 | condition: { 66 | urlPattern: { pathname: '/favicon.ico' }, 67 | }, 68 | source: 'race-network-and-fetch-handler', 69 | }, 70 | ]); 71 | console.log('[SW] Routes registered'); 72 | } catch (err) { 73 | console.error(err); 74 | } 75 | } else { 76 | console.error('[SW] No static routing support'); 77 | } 78 | 79 | }); 80 | 81 | // PRECACHING 82 | 83 | // Precache and serve resources from __WB_MANIFEST array 84 | precacheAndRoute(self.__WB_MANIFEST); 85 | 86 | // NAVIGATION ROUTING 87 | 88 | // This assumes /index.html has been precached. 89 | const navHandler = createHandlerBoundToURL('/index.html'); 90 | const navigationRoute = new NavigationRoute(navHandler, { 91 | denylist: [ 92 | new RegExp('/account'), 93 | new RegExp('/admin'), 94 | new RegExp('/login'), 95 | new RegExp('/logout'), 96 | new RegExp('/.auth'), 97 | new RegExp('/aboutme'), 98 | new RegExp('/400.html'), 99 | new RegExp('/404.html'), 100 | new RegExp('/privacy.html'), 101 | ], // Also might be specified explicitly via allowlist 102 | }); 103 | registerRoute(navigationRoute); 104 | 105 | // STATIC RESOURCES 106 | 107 | googleFontsCache({ cachePrefix: 'wb6-gfonts' }); 108 | 109 | // APP SHELL UPDATE FLOW 110 | 111 | addEventListener('message', (event) => { 112 | if (event.data && event.data.type === 'SKIP_WAITING') { 113 | self.skipWaiting(); 114 | } 115 | }); 116 | 117 | // BACKGROUND SYNC 118 | 119 | const messageAboutFailPlugin = { 120 | fetchDidFail: async ({ originalRequest, request, error, event, state }) => { 121 | messageClient(event, 'REQUEST_FAILED'); 122 | }, 123 | }; 124 | 125 | // Instantiating and configuring plugin 126 | const bgSyncPlugin = new BackgroundSyncPlugin('feedbackQueue', { 127 | maxRetentionTime: 24 * 60, // Retry for max of 24 Hours (specified in minutes) 128 | 129 | onSync: async ({ queue }) => { 130 | // Run standard replay 131 | await queue.replayRequests(); 132 | 133 | self.clients.matchAll().then((clients) => { 134 | clients.forEach((client) => 135 | client.postMessage({ type: 'REPLAY_COMPLETED' }) 136 | ); 137 | }); 138 | }, 139 | }); 140 | 141 | // Registering a route for retries 142 | registerRoute( 143 | ({ url }) => url.pathname.startsWith('/api/feedback'), 144 | new NetworkFirst({ 145 | plugins: [bgSyncPlugin, messageAboutFailPlugin], 146 | }), 147 | 'POST' 148 | ); 149 | 150 | googleAnalytics.initialize(); 151 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import './App.css'; 3 | import Home from './Home.js'; 4 | import About from './About.js'; 5 | import Account from './Account.js'; 6 | import Admin from './Admin.js'; 7 | import Login from './Login.js'; 8 | import Upload from './Upload.js'; 9 | import log from 'loglevel'; 10 | import { isMobile } from 'react-device-detect'; 11 | import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; 12 | import React, { useEffect, useState } from 'react'; 13 | import { Workbox } from 'workbox-window'; 14 | import { ToastContainer, toast } from 'react-toastify'; 15 | import 'react-toastify/dist/ReactToastify.css'; 16 | import { getAppInsights } from './TelemetryService'; 17 | import TelemetryProvider from './telemetry-provider'; 18 | 19 | function App() { 20 | const query = new URLSearchParams(window.location.search); 21 | const isDebug = query.get('debug') === 'true'; 22 | const isForcedViz = query.get('viz') === 'true'; 23 | const testBPM = query.get('bpm'); 24 | 25 | log.setDefaultLevel(isDebug ? 'info' : 'error'); 26 | 27 | const [appInsights, setAppInsights] = useState(null); 28 | 29 | useEffect(() => { 30 | if ('serviceWorker' in navigator) { 31 | const wb = new Workbox('/sw.js'); 32 | 33 | const refreshPage = () => { 34 | wb.addEventListener('controlling', (event) => { 35 | window.location.reload(); 36 | }); 37 | 38 | wb.messageSkipWaiting(); 39 | }; 40 | 41 | const Msg = () => ( 42 |
43 | Updated app is available   44 | 45 |
46 | ); 47 | 48 | const showSkipWaitingPrompt = (event) => { 49 | toast.info(); 50 | }; 51 | 52 | wb.addEventListener('waiting', showSkipWaitingPrompt); 53 | 54 | wb.addEventListener('message', (event) => { 55 | if (!event.data) { 56 | return; 57 | } 58 | if (event.data.type === 'REPLAY_COMPLETED') { 59 | toast.success( 60 | 'Your feedback was sent after the connection is restored' 61 | ); 62 | } 63 | if (event.data.type === 'REQUEST_FAILED') { 64 | toast.warning( 65 | 'Your feedback will be sent after the connection is restored' 66 | ); 67 | } 68 | }); 69 | 70 | wb.register() 71 | .then((registration) => {}) 72 | .catch((err) => { 73 | console.error(err); 74 | }); 75 | } 76 | 77 | 78 | const [navTiming] = window.performance.getEntriesByType("navigation"); 79 | console.log(navTiming) 80 | 81 | }, []); 82 | 83 | return ( 84 | 85 | { 88 | let appInsightsInstance = getAppInsights(); 89 | appInsightsInstance.trackPageView(); 90 | setAppInsights(appInsightsInstance); 91 | }} 92 | > 93 |
94 |

95 | BPM Techno — Real-Time BPM Counter 96 |

97 | 98 | ? 99 | 100 |
101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 127 | 128 | 129 | 130 | 131 | 132 |
133 |
134 |
135 | 136 | {!isDebug ? ( 137 |

138 | Made in 🇳🇴  by  139 | Maxim Salnikov |  140 | GitHub 141 |

142 | ) : ( 143 |

Debugging mode

144 | )} 145 |
146 | 147 | 148 |
149 |
150 | ); 151 | } 152 | 153 | export default App; 154 | -------------------------------------------------------------------------------- /public/privacy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | BPM Techno - Free Online Real-Time BPM Counter for DJ 12 | 13 | 14 | 18 | 19 | 67 | 68 |
69 | 70 |

Privacy Policy

71 | 72 |

Maxim Salnikov operates the BPM Techno application, which provides the SERVICE.

73 | 74 |

This page is used to inform application visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service, the BPM Techno application.

75 | 76 |

If you choose to use our Service, then you agree to the collection and use of information in relation with this policy. The Personal Information that we collect are used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.

77 | 78 |

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at BPM Techno application, unless otherwise defined in this Privacy Policy.

79 | 80 |

Information Collection and Use

81 | 82 |

For a better experience while using our Service, we may require you to provide us with certain personally identifiable information, including but not limited to your name, phone number, and postal address. The information that we collect will be used to contact or identify you.

83 | 84 |

Log Data

85 | 86 |

We want to inform you that whenever you visit our Service, we collect information that your browser sends to us that is called Log Data. This Log Data may include information such as your computer’s Internet Protocol ("IP") address, browser version, pages of our Service that you visit, the time and date of your visit, the time spent on those pages, and other statistics.

87 | 88 |

Cookies

89 | 90 |

Cookies are files with small amount of data that is commonly used an anonymous unique identifier. These are sent to your browser from the application that you visit and are stored on your computer’s hard drive.

91 | 92 |

Our application uses these "cookies" to collection information and to improve our Service. You have the option to either accept or refuse these cookies, and know when a cookie is being sent to your computer. If you choose to refuse our cookies, you may not be able to use some portions of our Service.

93 | 94 |

Service Providers

95 | 96 |

We may employ third-party companies and individuals due to the following reasons:

97 | 98 | 104 | 105 |

We want to inform our Service users that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.

106 | 107 |

Security

108 | 109 |

We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.

110 | 111 |

Links to Other Sites

112 | 113 |

Our Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by us. Therefore, we strongly advise you to review the Privacy Policy of these websites. We have no control over, and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.

114 | 115 |

Changes to This Privacy Policy

116 | 117 |

We may update our Privacy Policy from time to time. Thus, we advise you to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately, after they are posted on this page.

118 | 119 |

Contact Us

120 | 121 |

If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.

122 | 123 |
124 | 125 | 126 | -------------------------------------------------------------------------------- /src/Home.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import './Home.css'; 3 | import Feedback from './Feedback.js'; 4 | import React, { useEffect, useState } from 'react'; 5 | import RealTimeBPMAnalyzer from 'realtime-bpm-analyzer'; 6 | import AudioMotionAnalyzer from 'audiomotion-analyzer'; 7 | import { ToastContainer, toast } from 'react-toastify'; 8 | import 'react-toastify/dist/ReactToastify.css'; 9 | import ReactHintFactory from 'react-hint'; 10 | import 'react-hint/css/index.css'; 11 | import './custom-hint.css'; 12 | import ReactGA from 'react-ga4'; 13 | import AdLink from './AdLink.js'; 14 | 15 | const ReactHint = ReactHintFactory(React); 16 | 17 | function Home(props) { 18 | let log = props.log; 19 | 20 | const isMobile = props.isMobile; 21 | const isForcedViz = props.isForcedViz; 22 | const testBPM = props.testBPM; 23 | const appInsights = props.appInsights; 24 | 25 | let context; 26 | let input; 27 | let scriptProcessorNode; 28 | const bufferSize = isMobile ? 16384 : 4096; 29 | 30 | useEffect(() => { 31 | ReactGA.event('select_content', { 32 | content_type: 'mode', 33 | item_id: 'realtime', 34 | }); 35 | }, []); 36 | 37 | useEffect(() => { 38 | appInsights?.trackEvent({ 39 | name: 'detect', 40 | properties: { 41 | content_type: 'mode', 42 | item_id: 'realtime', 43 | }, 44 | }); 45 | }, [appInsights]); 46 | 47 | const startListening = async () => { 48 | if (navigator.mediaDevices.getUserMedia) { 49 | try { 50 | window.AudioContext = window.AudioContext || window.webkitAudioContext; 51 | context = new window.AudioContext(); 52 | 53 | const stream = await navigator.mediaDevices.getUserMedia({ 54 | audio: true, 55 | }); 56 | 57 | onStream(stream); 58 | 59 | if (!isMobile || isForcedViz) { 60 | const audioMotionGradientOptions = { 61 | bgColor: '#0D4C73', 62 | dir: 'v', 63 | colorStops: [ 64 | { pos: 0.8, color: '#35748C' }, 65 | { pos: 0.6, color: '#F2B680' }, 66 | { pos: 0.4, color: '#D98C5F' }, 67 | { pos: 0.2, color: '#8C5230' }, 68 | ], 69 | }; 70 | 71 | const audioMotion = new AudioMotionAnalyzer( 72 | document.getElementById('AudioMotionAnalyzer') 73 | ); 74 | 75 | audioMotion.registerGradient('my-grad', audioMotionGradientOptions); 76 | 77 | audioMotion.setOptions({ 78 | gradient: 'my-grad', 79 | height: window.innerHeight / 4, 80 | showBgColor: false, 81 | overlay: true, 82 | mode: 6, 83 | lumiBars: false, 84 | showLeds: true, 85 | showScaleX: false, 86 | loRes: true, 87 | }); 88 | 89 | audioMotion.setLedParams({ 90 | maxLeds: 20, 91 | spaceV: 1, 92 | spaceH: 2, 93 | }); 94 | 95 | const micStream = 96 | audioMotion.audioCtx.createMediaStreamSource(stream); 97 | audioMotion.connectInput(micStream); 98 | audioMotion.volume = 0; 99 | } 100 | 101 | setIsListening(true); 102 | setIsShowingInit(false); 103 | } catch (err) { 104 | log.error(`${err.name}: ${err.message}`); 105 | } 106 | } else { 107 | toast.error('No luck with accessing audio in your browser...'); 108 | log.error('Browser is not supported'); 109 | } 110 | }; 111 | 112 | const [threshold, setThreshold] = useState(0); 113 | const [primaryBPM, setPrimaryBPM] = useState(testBPM || ``); 114 | const [secondaryBPM, setSecondaryBPM] = useState(``); 115 | const [isListening, setIsListening] = useState(false); 116 | 117 | const [isShowingInit, setIsShowingInit] = useState(true); 118 | const [isResultReady, setIsResultReady] = useState(testBPM ? true : false); 119 | 120 | const [isSampleVisible, setSampleVisible] = useState(false); 121 | 122 | const toggleSampleVisibility = () => { 123 | setSampleVisible(!isSampleVisible); 124 | }; 125 | 126 | const stopListening = () => { 127 | setIsListening(false); 128 | setIsShowingInit(true); 129 | window.location.reload(); 130 | }; 131 | 132 | const onStream = (stream) => { 133 | input = context.createMediaStreamSource(stream); 134 | scriptProcessorNode = context.createScriptProcessor(bufferSize, 1, 1); 135 | 136 | input.connect(scriptProcessorNode); 137 | scriptProcessorNode.connect(context.destination); 138 | 139 | const onAudioProcess = new RealTimeBPMAnalyzer({ 140 | debug: props.isDebug, 141 | scriptNode: { 142 | bufferSize: bufferSize, 143 | numberOfInputChannels: 1, 144 | numberOfOutputChannels: 1, 145 | }, 146 | computeBPMDelay: 5000, 147 | stabilizationTime: 10000, 148 | continuousAnalysis: true, 149 | pushTime: 1000, 150 | pushCallback: (err, bpm, threshold) => { 151 | if (err) { 152 | log.warn(`${err.name}: ${err.message}`); 153 | 154 | setIsResultReady(false); 155 | return; 156 | } 157 | 158 | if (bpm && bpm.length) { 159 | setIsResultReady(true); 160 | setThreshold(Math.round(threshold * 100) / 100); 161 | 162 | setPrimaryBPM(`${bpm[0].tempo}`); 163 | setSecondaryBPM(`${bpm[1].tempo}`); 164 | 165 | log.info(bpm); 166 | log.info(`Threshold, ${threshold}`); 167 | 168 | ReactGA.event('detect', { 169 | mode: 'realtime', 170 | bpm: bpm[0].tempo, 171 | threshold: threshold, 172 | }); 173 | appInsights.trackEvent({ 174 | name: 'detect', 175 | properties: { 176 | mode: 'realtime', 177 | bpm: bpm[0].tempo, 178 | threshold: threshold, 179 | }, 180 | }); 181 | } 182 | }, 183 | onBpmStabilized: (threshold) => { 184 | onAudioProcess.clearValidPeaks(threshold); 185 | }, 186 | }); 187 | 188 | scriptProcessorNode.onaudioprocess = (e) => { 189 | onAudioProcess.analyze(e); 190 | }; 191 | }; 192 | 193 | return ( 194 |
195 | {isShowingInit ? ( 196 |
197 | 205 | 206 |

You will be asked to provide access to your microphone.

207 |

App does not send any audio stream data to the servers.

208 | 209 |

210 | 211 |

212 |
213 | ) : ( 214 |
215 |

216 | {isResultReady ? primaryBPM : null} 217 |

218 |

{isResultReady ? 'BPM' : 'Listening...'}

219 | 220 | {!isResultReady && primaryBPM ? ( 221 |

222 | Last: 223 | {primaryBPM} 224 | BPM 225 |

226 | ) : null} 227 | 228 |

229 | 230 |

231 | 232 | 235 | 236 | {primaryBPM ? ( 237 | 243 | ) : null} 244 | 245 |
246 |
247 | 248 | {isSampleVisible ? ( 249 |

250 | 251 | Hide sample file 252 | 253 |
254 | 259 |
260 | 261 | Play it loud! It takes 5-30 seconds to detect correct BPM (120).{' '} 262 | 263 |

264 | ) : ( 265 |

266 | 267 | Show sample file 268 | 269 |

270 | )} 271 |
272 | )} 273 | 274 | 275 | 276 | {!isMobile ? ( 277 | 283 | ) : null} 284 |
285 | ); 286 | } 287 | 288 | export default Home; 289 | -------------------------------------------------------------------------------- /src/About.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { SeverityLevel } from '@microsoft/applicationinsights-web'; 3 | 4 | function About(props) { 5 | const [clientPrincipal, setClientPrincipal] = useState(null); 6 | 7 | let appInsights = props.appInsights; 8 | 9 | function trackException() { 10 | appInsights.trackException({ 11 | error: new Error('some error'), 12 | severityLevel: SeverityLevel.Error, 13 | }); 14 | } 15 | 16 | function trackTrace() { 17 | appInsights.trackTrace({ 18 | message: 'some trace', 19 | severityLevel: SeverityLevel.Information, 20 | }); 21 | } 22 | 23 | function trackEvent() { 24 | appInsights.trackEvent({ name: 'some event' }); 25 | } 26 | 27 | function throwError() { 28 | let foo = { 29 | field: { bar: 'value' }, 30 | }; 31 | 32 | // This will crash the app; the error will show up in the Azure Portal 33 | return foo.fielld.bar; 34 | } 35 | 36 | function ajaxRequest() { 37 | let xhr = new XMLHttpRequest(); 38 | xhr.open('GET', 'https://httpbin.org/status/200'); 39 | xhr.send(); 40 | } 41 | 42 | function fetchRequest() { 43 | fetch('https://httpbin.org/status/200'); 44 | } 45 | 46 | async function fetchClientPrincipal() { 47 | try { 48 | const res = await fetch('/.auth/me'); 49 | const json = await res.json(); 50 | if (json.clientPrincipal) { 51 | setClientPrincipal(json.clientPrincipal); 52 | } 53 | } catch (e) { 54 | if (window.location.hostname === 'localhost') { 55 | console.warn( 56 | "Can't access the auth endpoint. For local development, please use the Static Web Apps CLI to emulate authentication: https://github.com/azure/static-web-apps-cli" 57 | ); 58 | } else { 59 | console.error(`Failed to unpack JSON.`, e); 60 | } 61 | } 62 | } 63 | 64 | return ( 65 |
66 |

This is a 3-in-1 project

67 |
    68 |
  1. 69 | A real product for DJs to help with identifying BPM 70 | (beats per minute) of the track currently playing. 71 |
  2. 72 |
  3. 73 | A demo and playground for the  74 | 75 | Azure Static Web Apps (SWA) 76 | 77 |   service. 78 |
  4. 79 |
  5. 80 | Proof of concept for a Progressive Web App (PWA) 81 | driven by Workbox-powered service worker. 82 |
  6. 83 |
84 |

Author and credits

85 |

86 | Built by Maxim Salnikov. I 87 | will be happy to see your feedback and contributions in{' '} 88 | 89 | the project GitHub repo 90 | 91 | . 92 |

93 |

Service links for Azure Static Web Apps demo

94 |

95 | 96 | GitHub repo with a step-by-step demo guide 97 | 98 |

99 | 195 |

Useful links

196 |

Here are some links to help you get started:

197 | 247 |

248 | Questions? Contact  249 | Maxim Salnikov 250 |

251 |
252 | ); 253 | } 254 | 255 | export default About; 256 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BPM Techno - Free Online Real-Time BPM Counter for DJ 2 | 3 |

4 | 5 |

6 | 7 | This is a 3-in-1 project: 8 | 9 | 1. A real product for DJs to help with identifying BPM (beats per minute) of the track currently playing. 10 | 2. A real-world demo and a playground for [Azure Static Web Apps](https://azure.microsoft.com/en-us/services/app-service/static/?ocid=aid3040965) service. 11 | 3. Proof of concept for a Progressive Web App (PWA) driven by Workbox-powered service worker. 12 | 13 | ### Web application (installable, offline-ready) 14 | 15 | [BPMTech.no](https://bpmtech.no) - Free Online Real-Time BPM Counter for DJ 16 | 17 | ### Video demo 18 | 19 | [](https://youtu.be/o9BIK5QENJU) 20 | 21 | *(click to watch on YouTube)* 22 | 23 | ## Flow and resources for the Azure Static Web Apps features demo 24 | 25 | ### Installation 26 | 27 | ```shell 28 | git clone https://github.com/webmaxru/bpm-counter.git 29 | cd bpm-counter 30 | npm install 31 | 32 | # Installing tools for Static Web Apps and Azure Functions 33 | npm install -g @azure/static-web-apps-cli 34 | npm install -g azure-functions-core-tools@3 --unsafe-perm true 35 | ``` 36 | 37 | ### Starting local development server 38 | 39 | ```shell 40 | # Instead of CRA's "npm start" we use SWA CLI's command to start everything at once 41 | swa start http://localhost:3000 --run "npm start" --api-location ./api 42 | ``` 43 | 44 | Open [http://localhost:4280](http://localhost:4280) in your browser. 45 | 46 | ### Deploying to Azure 47 | 48 | To deploy this project to Azure, you need to fork this repo to your own GitHub account. You will also need an Azure subscription. If you don't have it, you can [get Azure subscription here for free](https://aka.ms/free-azure-pass) with $200 credit. 49 | 50 | *Please note, that Azure Static Web Apps service has a [generous free tier](https://azure.microsoft.com/en-us/pricing/details/app-service/static/?ocid=aid3040965) which is enough for many types of the personal projects.* 51 | 52 | After you have the repo in your GitHub account, and Azure subscription ready, use an [Azure Static Web Apps extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps) or [Azure Portal](https://portal.azure.com/?feature.customportal=false#create/Microsoft.StaticApp) to create an SWA resource. 53 | 54 |

55 | 56 | - or - 57 | 58 |

59 | 60 | Use the following parameters: 61 | 62 | - App location: **/** 63 | - Api location: **api** 64 | - Output location: **build** 65 | 66 | What will happen: 67 | 68 | - In a few seconds, you will see the website deployed to Azure with a development URL like *random-word.azurestaticapps.net* ([example](https://mango-mud-0136f961e.azurestaticapps.net/)). You can connect your own custom domain to it using "Custom domain" option in the portal. 69 | 70 | 71 | 72 | - A GitHub Actions file will be created in `.github/workflows` folder of your repo. Similar to [the one](https://github.com/webmaxru/bpm-counter/blob/main/.github/workflows/azure-static-web-apps-mango-mud-0136f961e.yml) in the original repo. 73 | 74 | You are now ready to explore the Azure Static Web Apps features. 75 | 76 | ### Automatic deployment on code change 77 | 78 | 1. Do any code change in the application. Something that will be clearly visible on the first page, for example [app name](https://github.com/webmaxru/bpm-counter/blob/main/src/App.js#L70) in the header. 79 | 2. Commit and push the changes to `main` branch (or the branch you specified during resource creation). 80 | 3. Go to [Actions](https://github.com/webmaxru/bpm-counter/actions) page of your repo to make sure that the workflow is running. 81 | 82 | [](https://github.com/webmaxru/bpm-counter/actions) 83 | 84 | 4. On completion, open your website in a browser, you will see the new version. 85 | 86 | **Please note, this is a service worker-driven application, so you will see the prompt to reload the page.** 87 | 88 | 89 | 90 | ### Staging environments 91 | 92 | You can review pull requests in pre-production environment before they are merged to the main branch. 93 | 94 | 1. Create a branch for your new feature. 95 | 96 | ```shell 97 | git checkout -b new-feature 98 | ``` 99 | 100 | Do any code change in the application. Something that will be clearly visible on the first page, for example [change background color](https://github.com/webmaxru/bpm-counter/blob/main/src/index.css#L16). 101 | 102 | 2. Commit and push the changes to the branch. 103 | 104 | ```shell 105 | git add . 106 | git commit -m "New feature" 107 | git push origin new-feature 108 | ``` 109 | 110 | 3. Go to you GitHub repo page and [create a new Pull Request](https://github.com/webmaxru/bpm-counter/compare/new-feature?expand=1) from the branch. 111 | 112 | 4. Go to [Actions](https://github.com/webmaxru/bpm-counter/actions) page of your repo to make sure that the workflow is running. 113 | 114 | [](https://github.com/webmaxru/bpm-counter/actions) 115 | 116 | 5. On completion, you will have a new version of the website deployed to Azure to a [new URL](https://mango-mud-0136f961e-2.westus2.azurestaticapps.net/). You can get this URL either from the workflow output on Azure or in the Azure Portal on Environments tab. GitHub Actions bot will also post this URL to your Pull Request [comments](https://github.com/webmaxru/bpm-counter/pull/2). 117 | 118 | 6. Now, you can run various tests on your new version. 119 | 120 | If the new version looks good and you merge this Pull Request to the main (tracked by SWA) branch, the workflow will automatically deploy the new version to this tracked branch and delete staging environment. 121 | 122 | 123 | 124 | **Please note, staged versions of your application are currently accessible publicly by their URL, even if your GitHub repository is private.** 125 | 126 | [🗎 Documentation](https://docs.microsoft.com/en-us/azure/static-web-apps/review-publish-pull-requests?ocid=aid3040965) 127 | 128 | ### API Using Azure Functions 129 | 130 | You can use the [Azure Functions](https://azure.microsoft.com/en-us/services/functions/?ocid=aid3040965) to build your own API for your static web app. The simplest option is using Managed Functions option: all Azure Functions you create in `api` directory will be automatically deployed to the SWA. In this project, we use [`feedback` function](https://github.com/webmaxru/bpm-counter/blob/main/api/feedback/index.js) to gather statistics on correct or wrong BPMs detected. 131 | 132 | To test it even without the music playing, you can pass a "hardcoded" BPM value to the application: [by using bpm parameter](https://bpmtech.no/?bpm=120). How to test it: 133 | 134 | 1. Click "Start listening" button. 135 | 2. Click "Thumbs up" button. 136 | 3. Check the network POST request made to `https://bpmtech.no/api/feedback` endpoint and its response. 137 | 138 | How to create a new API function: 139 | 140 | 1. Use "Create HTTP Function" button in VS Code extension. 141 | 142 | 143 | 144 | 2. Follow the creation wizard. 145 | 3. Write your code. 146 | 4. Commit and push the changes to the branch. 147 | 5. Your function will be automatically deployed to the SWA. 148 | 149 | 150 | 151 | [🗎 Documentation](https://docs.microsoft.com/en-us/azure/static-web-apps/add-api?ocid=aid3040965) 152 | 153 | ### Routing 154 | 155 | Azure SWA supports custom routing which allows you to: 156 | 157 | - Set up redirects 158 | - Organize navigation fallback for the single-page applications 159 | - Set up custom headers 160 | - Register MIME types 161 | - Define custom pages for HTTP errors 162 | - Protect resources by a role-based access control (RBAC) 163 | 164 | You configure the rules in [staticwebapp.config.json](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json) which you can put anywhere in the application folder of the repo, there is no requirement to put it in the output (public) folder). 165 | 166 | How to test it: 167 | 168 | 1. Go directly to [/about](https://bpmtech.no/about) page. You will see the application, not error 404 because of the [navigation fallback](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L39) rule. 169 | 2. Go to [any non-existing resource](https://bpmtech.no/images/nopic.jpg) from the navigationFallback exclude list. You will see the custom 404 error page configured in [this](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L45) rule. 170 | 3. Check the response headers. They contain "X-Powered-By: Maxim Salnikov and Azure Static Web Apps" set on [this line](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L59). 171 | 4. Go to [/aboutme](https://bpmtech.no/aboutme) page. You will be redirected to [/about](https://bpmtech.no/about) because of [this](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L34) rule. 172 | 173 | **Please note, the hosted application is controlled by a service worker. So after the first load, the routing might look not exactly like explained above. To test the app without a service worker, start a new browser session in Private/Incognito mode.** 174 | 175 | [🗎 Documentation](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-routing-overview?ocid=aid3040965) 176 | 177 | ### Authentication 178 | 179 | With the help of Azure Static Web Apps, you can protect your application resources with the role-based access control (RBAC). 180 | 181 | Setting up authentication: 182 | 183 | 1. You specify the role(s) needed to access particular URLs in the [staticwebapp.config.json](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json) file. There are two built-in roles: `anonymous` (for all users) and `authenticated` (for those who are logged in). 184 | 2. If the user tries to access URL without the required role, they will get error 401. You might want to set up a [redirect](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L48) to the login page. 185 | 3. To let users log in, you direct users to one of the built-in identity providers (Azure Active Directory, GitHub, Twitter) login pages. For example, [/.auth/login/twitter](https://bpmtech.no/.auth/login/twitter). (You can also create a custom URL for this page using [routing rules](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L22).). The folder `.auth` on your Azure SWA project is built-in, it's so called *system folder* which contains some useful endpoints. 186 | 4. After logging in using the selected identity provider and giving consent on sharing personal information (email or user handle), the user will be redirected back to the application. And if the role is correctly set, they will get an access to the requested URL. 187 | 5. To give user a custom role (for example, `administrator`), you use "Role management" tab in the Azure Portal. Click on "Invite" button, fill in the form and click "Generate". You will receive a link to send to the user to accept the role. 188 | 189 | 190 | 191 | You can manage the users and roles in the "Role management" tab. 192 | 193 | 194 | 195 | 6. You can read authenticated user credentials (for example to implement some logic in UI) by sending request to [/.auth/me](https://bpmtech.no/.auth/me) endpoint. To check authentication info of the API calls, you read the `x-ms-client-principal` header in the request. 196 | 7. To log out, you redirect users to [/.auth/logout](https://bpmtech.no/.auth/logout) page. 197 | 8. To remove personally identifying information (email or user handle) from the application, you direct users to the link that is related to a particular identity provider: [/.auth/purge/twitter](https://bpmtech.no/.auth/purge/twitter). 198 | 199 | Demo: 200 | 201 | 1. Try to access [/account](https://bpmtech.no/account) page. It's configured to be available only for `authenticated` users by [this rule](https://github.com/webmaxru/bpm-counter/blob/main/src/staticwebapp.config.json#L4). You will be redirected to the Twitter login page and asked for consent. 202 | 2. After logging in with Twitter, you will be redirected back to the application, and now can access [/account](https://bpmtech.no/account) page. 203 | 3. Open [/.auth/me](https://bpmtech.no/.auth/me) URL in the separate tab to see the information returned by the server. 204 | 4. Log out by going to [/.auth/logout](https://bpmtech.no/.auth/logout) URL. 205 | 5. You can repeat the steps above to test the custom role needed to access [/admin](https://bpmtech.no/admin) page. In this case, you need to give the user the role `administrator` as described above. 206 | 207 | [🗎 Documentation](https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-authorization?ocid=aid3040965) 208 | 209 | ## React-only version (no cloud) 210 | 211 | In the project directory, you can run: 212 | 213 | ### `npm run start` 214 | 215 | Runs the app in the development mode.\ 216 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 217 | 218 | The page will reload if you make edits.\ 219 | You will also see any lint errors in the console. 220 | 221 | The service worker is not in use in the development environment. 222 | 223 | ### `npm run build` 224 | 225 | Builds the app for production to the `build` folder.\ 226 | It correctly bundles React in production mode and optimizes the build for the best performance. 227 | 228 | The build is minified and the filenames include the hashes.\ 229 | Your app is ready to be deployed! 230 | 231 | The production-ready service worker will also be generated. 232 | 233 | ## About 234 | 235 | ### Credits 236 | 237 | - Real-time BPM detection is based on [realtime-bpm-analyzer](https://www.npmjs.com/package/realtime-bpm-analyzer) library by [David Lepaux](https://github.com/dlepaux) 238 | - BPM detection in the files is based on [bpm-detective](https://www.npmjs.com/package/bpm-detective) library by [Carl Törnqvist](https://github.com/tornqvist/) 239 | - Spectrum analyzer is based [audioMotion-analyzer](https://www.npmjs.com/package/audiomotion-analyzer) library by [Henrique Vianna](https://github.com/hvianna/) 240 | 241 | ### Author 242 | 243 | [Maxim Salnikov](https://twitter.com/webmaxru). Feel free to contact me if you have any questions about the project, PWA, Azure Static Web Apps. 244 | -------------------------------------------------------------------------------- /api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "api", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "applicationinsights": "^2.3.5" 12 | }, 13 | "devDependencies": {} 14 | }, 15 | "node_modules/@azure/abort-controller": { 16 | "version": "1.1.0", 17 | "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", 18 | "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", 19 | "dependencies": { 20 | "tslib": "^2.2.0" 21 | }, 22 | "engines": { 23 | "node": ">=12.0.0" 24 | } 25 | }, 26 | "node_modules/@azure/core-auth": { 27 | "version": "1.4.0", 28 | "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", 29 | "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", 30 | "dependencies": { 31 | "@azure/abort-controller": "^1.0.0", 32 | "tslib": "^2.2.0" 33 | }, 34 | "engines": { 35 | "node": ">=12.0.0" 36 | } 37 | }, 38 | "node_modules/@azure/core-http": { 39 | "version": "2.2.7", 40 | "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.2.7.tgz", 41 | "integrity": "sha512-TyGMeDm90mkRS8XzSQbSMD+TqnWL1XKGCh0x0QVGMD8COH2yU0q5SaHm/IBEBkzcq0u73NhS/p57T3KVSgUFqQ==", 42 | "dependencies": { 43 | "@azure/abort-controller": "^1.0.0", 44 | "@azure/core-auth": "^1.3.0", 45 | "@azure/core-tracing": "1.0.0-preview.13", 46 | "@azure/core-util": "^1.1.0", 47 | "@azure/logger": "^1.0.0", 48 | "@types/node-fetch": "^2.5.0", 49 | "@types/tunnel": "^0.0.3", 50 | "form-data": "^4.0.0", 51 | "node-fetch": "^2.6.7", 52 | "process": "^0.11.10", 53 | "tough-cookie": "^4.0.0", 54 | "tslib": "^2.2.0", 55 | "tunnel": "^0.0.6", 56 | "uuid": "^8.3.0", 57 | "xml2js": "^0.4.19" 58 | }, 59 | "engines": { 60 | "node": ">=12.0.0" 61 | } 62 | }, 63 | "node_modules/@azure/core-tracing": { 64 | "version": "1.0.0-preview.13", 65 | "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", 66 | "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", 67 | "dependencies": { 68 | "@opentelemetry/api": "^1.0.1", 69 | "tslib": "^2.2.0" 70 | }, 71 | "engines": { 72 | "node": ">=12.0.0" 73 | } 74 | }, 75 | "node_modules/@azure/core-util": { 76 | "version": "1.1.1", 77 | "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", 78 | "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", 79 | "dependencies": { 80 | "@azure/abort-controller": "^1.0.0", 81 | "tslib": "^2.2.0" 82 | }, 83 | "engines": { 84 | "node": ">=12.0.0" 85 | } 86 | }, 87 | "node_modules/@azure/logger": { 88 | "version": "1.0.3", 89 | "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", 90 | "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", 91 | "dependencies": { 92 | "tslib": "^2.2.0" 93 | }, 94 | "engines": { 95 | "node": ">=12.0.0" 96 | } 97 | }, 98 | "node_modules/@microsoft/applicationinsights-web-snippet": { 99 | "version": "1.0.1", 100 | "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz", 101 | "integrity": "sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==" 102 | }, 103 | "node_modules/@opentelemetry/api": { 104 | "version": "1.2.0", 105 | "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.2.0.tgz", 106 | "integrity": "sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g==", 107 | "engines": { 108 | "node": ">=8.0.0" 109 | } 110 | }, 111 | "node_modules/@opentelemetry/core": { 112 | "version": "1.7.0", 113 | "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.7.0.tgz", 114 | "integrity": "sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ==", 115 | "dependencies": { 116 | "@opentelemetry/semantic-conventions": "1.7.0" 117 | }, 118 | "engines": { 119 | "node": ">=14" 120 | }, 121 | "peerDependencies": { 122 | "@opentelemetry/api": ">=1.0.0 <1.3.0" 123 | } 124 | }, 125 | "node_modules/@opentelemetry/resources": { 126 | "version": "1.7.0", 127 | "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.7.0.tgz", 128 | "integrity": "sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg==", 129 | "dependencies": { 130 | "@opentelemetry/core": "1.7.0", 131 | "@opentelemetry/semantic-conventions": "1.7.0" 132 | }, 133 | "engines": { 134 | "node": ">=14" 135 | }, 136 | "peerDependencies": { 137 | "@opentelemetry/api": ">=1.0.0 <1.3.0" 138 | } 139 | }, 140 | "node_modules/@opentelemetry/sdk-trace-base": { 141 | "version": "1.7.0", 142 | "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz", 143 | "integrity": "sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg==", 144 | "dependencies": { 145 | "@opentelemetry/core": "1.7.0", 146 | "@opentelemetry/resources": "1.7.0", 147 | "@opentelemetry/semantic-conventions": "1.7.0" 148 | }, 149 | "engines": { 150 | "node": ">=14" 151 | }, 152 | "peerDependencies": { 153 | "@opentelemetry/api": ">=1.0.0 <1.3.0" 154 | } 155 | }, 156 | "node_modules/@opentelemetry/semantic-conventions": { 157 | "version": "1.7.0", 158 | "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz", 159 | "integrity": "sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA==", 160 | "engines": { 161 | "node": ">=14" 162 | } 163 | }, 164 | "node_modules/@types/node": { 165 | "version": "18.8.4", 166 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.4.tgz", 167 | "integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==" 168 | }, 169 | "node_modules/@types/node-fetch": { 170 | "version": "2.6.2", 171 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", 172 | "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", 173 | "dependencies": { 174 | "@types/node": "*", 175 | "form-data": "^3.0.0" 176 | } 177 | }, 178 | "node_modules/@types/node-fetch/node_modules/form-data": { 179 | "version": "3.0.1", 180 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 181 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 182 | "dependencies": { 183 | "asynckit": "^0.4.0", 184 | "combined-stream": "^1.0.8", 185 | "mime-types": "^2.1.12" 186 | }, 187 | "engines": { 188 | "node": ">= 6" 189 | } 190 | }, 191 | "node_modules/@types/tunnel": { 192 | "version": "0.0.3", 193 | "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", 194 | "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", 195 | "dependencies": { 196 | "@types/node": "*" 197 | } 198 | }, 199 | "node_modules/applicationinsights": { 200 | "version": "2.3.5", 201 | "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.3.5.tgz", 202 | "integrity": "sha512-QU6EEZbobj9NL2o/XLIDStCMfwrrLwFrbJrDw9ih1wb5bz7v0cwUm6kPXiKtNAAny4hWp9/BtBhtFKvc3tWZ3w==", 203 | "dependencies": { 204 | "@azure/core-http": "^2.2.3", 205 | "@microsoft/applicationinsights-web-snippet": "^1.0.1", 206 | "@opentelemetry/api": "^1.0.4", 207 | "@opentelemetry/core": "^1.0.1", 208 | "@opentelemetry/sdk-trace-base": "^1.0.1", 209 | "@opentelemetry/semantic-conventions": "^1.0.1", 210 | "cls-hooked": "^4.2.2", 211 | "continuation-local-storage": "^3.2.1", 212 | "diagnostic-channel": "1.1.0", 213 | "diagnostic-channel-publishers": "1.0.5" 214 | }, 215 | "engines": { 216 | "node": ">=8.0.0" 217 | }, 218 | "peerDependencies": { 219 | "applicationinsights-native-metrics": "*" 220 | }, 221 | "peerDependenciesMeta": { 222 | "applicationinsights-native-metrics": { 223 | "optional": true 224 | } 225 | } 226 | }, 227 | "node_modules/async-hook-jl": { 228 | "version": "1.7.6", 229 | "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", 230 | "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", 231 | "dependencies": { 232 | "stack-chain": "^1.3.7" 233 | }, 234 | "engines": { 235 | "node": "^4.7 || >=6.9 || >=7.3" 236 | } 237 | }, 238 | "node_modules/async-listener": { 239 | "version": "0.6.10", 240 | "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", 241 | "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", 242 | "dependencies": { 243 | "semver": "^5.3.0", 244 | "shimmer": "^1.1.0" 245 | }, 246 | "engines": { 247 | "node": "<=0.11.8 || >0.11.10" 248 | } 249 | }, 250 | "node_modules/asynckit": { 251 | "version": "0.4.0", 252 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 253 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 254 | }, 255 | "node_modules/cls-hooked": { 256 | "version": "4.2.2", 257 | "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", 258 | "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", 259 | "dependencies": { 260 | "async-hook-jl": "^1.7.6", 261 | "emitter-listener": "^1.0.1", 262 | "semver": "^5.4.1" 263 | }, 264 | "engines": { 265 | "node": "^4.7 || >=6.9 || >=7.3 || >=8.2.1" 266 | } 267 | }, 268 | "node_modules/combined-stream": { 269 | "version": "1.0.8", 270 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 271 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 272 | "dependencies": { 273 | "delayed-stream": "~1.0.0" 274 | }, 275 | "engines": { 276 | "node": ">= 0.8" 277 | } 278 | }, 279 | "node_modules/continuation-local-storage": { 280 | "version": "3.2.1", 281 | "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", 282 | "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", 283 | "dependencies": { 284 | "async-listener": "^0.6.0", 285 | "emitter-listener": "^1.1.1" 286 | } 287 | }, 288 | "node_modules/delayed-stream": { 289 | "version": "1.0.0", 290 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 291 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 292 | "engines": { 293 | "node": ">=0.4.0" 294 | } 295 | }, 296 | "node_modules/diagnostic-channel": { 297 | "version": "1.1.0", 298 | "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz", 299 | "integrity": "sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ==", 300 | "dependencies": { 301 | "semver": "^5.3.0" 302 | } 303 | }, 304 | "node_modules/diagnostic-channel-publishers": { 305 | "version": "1.0.5", 306 | "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz", 307 | "integrity": "sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg==", 308 | "peerDependencies": { 309 | "diagnostic-channel": "*" 310 | } 311 | }, 312 | "node_modules/emitter-listener": { 313 | "version": "1.1.2", 314 | "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", 315 | "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", 316 | "dependencies": { 317 | "shimmer": "^1.2.0" 318 | } 319 | }, 320 | "node_modules/form-data": { 321 | "version": "4.0.0", 322 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 323 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 324 | "dependencies": { 325 | "asynckit": "^0.4.0", 326 | "combined-stream": "^1.0.8", 327 | "mime-types": "^2.1.12" 328 | }, 329 | "engines": { 330 | "node": ">= 6" 331 | } 332 | }, 333 | "node_modules/mime-db": { 334 | "version": "1.52.0", 335 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 336 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 337 | "engines": { 338 | "node": ">= 0.6" 339 | } 340 | }, 341 | "node_modules/mime-types": { 342 | "version": "2.1.35", 343 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 344 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 345 | "dependencies": { 346 | "mime-db": "1.52.0" 347 | }, 348 | "engines": { 349 | "node": ">= 0.6" 350 | } 351 | }, 352 | "node_modules/node-fetch": { 353 | "version": "2.6.7", 354 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 355 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 356 | "dependencies": { 357 | "whatwg-url": "^5.0.0" 358 | }, 359 | "engines": { 360 | "node": "4.x || >=6.0.0" 361 | }, 362 | "peerDependencies": { 363 | "encoding": "^0.1.0" 364 | }, 365 | "peerDependenciesMeta": { 366 | "encoding": { 367 | "optional": true 368 | } 369 | } 370 | }, 371 | "node_modules/process": { 372 | "version": "0.11.10", 373 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 374 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 375 | "engines": { 376 | "node": ">= 0.6.0" 377 | } 378 | }, 379 | "node_modules/psl": { 380 | "version": "1.9.0", 381 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", 382 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" 383 | }, 384 | "node_modules/punycode": { 385 | "version": "2.1.1", 386 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 387 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 388 | "engines": { 389 | "node": ">=6" 390 | } 391 | }, 392 | "node_modules/querystringify": { 393 | "version": "2.2.0", 394 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 395 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 396 | }, 397 | "node_modules/requires-port": { 398 | "version": "1.0.0", 399 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 400 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 401 | }, 402 | "node_modules/sax": { 403 | "version": "1.2.4", 404 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 405 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 406 | }, 407 | "node_modules/semver": { 408 | "version": "5.7.1", 409 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 410 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 411 | "bin": { 412 | "semver": "bin/semver" 413 | } 414 | }, 415 | "node_modules/shimmer": { 416 | "version": "1.2.1", 417 | "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", 418 | "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" 419 | }, 420 | "node_modules/stack-chain": { 421 | "version": "1.3.7", 422 | "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", 423 | "integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==" 424 | }, 425 | "node_modules/tough-cookie": { 426 | "version": "4.1.2", 427 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", 428 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", 429 | "dependencies": { 430 | "psl": "^1.1.33", 431 | "punycode": "^2.1.1", 432 | "universalify": "^0.2.0", 433 | "url-parse": "^1.5.3" 434 | }, 435 | "engines": { 436 | "node": ">=6" 437 | } 438 | }, 439 | "node_modules/tr46": { 440 | "version": "0.0.3", 441 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 442 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 443 | }, 444 | "node_modules/tslib": { 445 | "version": "2.4.0", 446 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 447 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 448 | }, 449 | "node_modules/tunnel": { 450 | "version": "0.0.6", 451 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", 452 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", 453 | "engines": { 454 | "node": ">=0.6.11 <=0.7.0 || >=0.7.3" 455 | } 456 | }, 457 | "node_modules/universalify": { 458 | "version": "0.2.0", 459 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", 460 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", 461 | "engines": { 462 | "node": ">= 4.0.0" 463 | } 464 | }, 465 | "node_modules/url-parse": { 466 | "version": "1.5.10", 467 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 468 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 469 | "dependencies": { 470 | "querystringify": "^2.1.1", 471 | "requires-port": "^1.0.0" 472 | } 473 | }, 474 | "node_modules/uuid": { 475 | "version": "8.3.2", 476 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 477 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 478 | "bin": { 479 | "uuid": "dist/bin/uuid" 480 | } 481 | }, 482 | "node_modules/webidl-conversions": { 483 | "version": "3.0.1", 484 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 485 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 486 | }, 487 | "node_modules/whatwg-url": { 488 | "version": "5.0.0", 489 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 490 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 491 | "dependencies": { 492 | "tr46": "~0.0.3", 493 | "webidl-conversions": "^3.0.0" 494 | } 495 | }, 496 | "node_modules/xml2js": { 497 | "version": "0.4.23", 498 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", 499 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", 500 | "dependencies": { 501 | "sax": ">=0.6.0", 502 | "xmlbuilder": "~11.0.0" 503 | }, 504 | "engines": { 505 | "node": ">=4.0.0" 506 | } 507 | }, 508 | "node_modules/xmlbuilder": { 509 | "version": "11.0.1", 510 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 511 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", 512 | "engines": { 513 | "node": ">=4.0" 514 | } 515 | } 516 | }, 517 | "dependencies": { 518 | "@azure/abort-controller": { 519 | "version": "1.1.0", 520 | "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", 521 | "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", 522 | "requires": { 523 | "tslib": "^2.2.0" 524 | } 525 | }, 526 | "@azure/core-auth": { 527 | "version": "1.4.0", 528 | "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", 529 | "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", 530 | "requires": { 531 | "@azure/abort-controller": "^1.0.0", 532 | "tslib": "^2.2.0" 533 | } 534 | }, 535 | "@azure/core-http": { 536 | "version": "2.2.7", 537 | "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.2.7.tgz", 538 | "integrity": "sha512-TyGMeDm90mkRS8XzSQbSMD+TqnWL1XKGCh0x0QVGMD8COH2yU0q5SaHm/IBEBkzcq0u73NhS/p57T3KVSgUFqQ==", 539 | "requires": { 540 | "@azure/abort-controller": "^1.0.0", 541 | "@azure/core-auth": "^1.3.0", 542 | "@azure/core-tracing": "1.0.0-preview.13", 543 | "@azure/core-util": "^1.1.0", 544 | "@azure/logger": "^1.0.0", 545 | "@types/node-fetch": "^2.5.0", 546 | "@types/tunnel": "^0.0.3", 547 | "form-data": "^4.0.0", 548 | "node-fetch": "^2.6.7", 549 | "process": "^0.11.10", 550 | "tough-cookie": "^4.0.0", 551 | "tslib": "^2.2.0", 552 | "tunnel": "^0.0.6", 553 | "uuid": "^8.3.0", 554 | "xml2js": "^0.4.19" 555 | } 556 | }, 557 | "@azure/core-tracing": { 558 | "version": "1.0.0-preview.13", 559 | "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", 560 | "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", 561 | "requires": { 562 | "@opentelemetry/api": "^1.0.1", 563 | "tslib": "^2.2.0" 564 | } 565 | }, 566 | "@azure/core-util": { 567 | "version": "1.1.1", 568 | "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", 569 | "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", 570 | "requires": { 571 | "@azure/abort-controller": "^1.0.0", 572 | "tslib": "^2.2.0" 573 | } 574 | }, 575 | "@azure/logger": { 576 | "version": "1.0.3", 577 | "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", 578 | "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", 579 | "requires": { 580 | "tslib": "^2.2.0" 581 | } 582 | }, 583 | "@microsoft/applicationinsights-web-snippet": { 584 | "version": "1.0.1", 585 | "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz", 586 | "integrity": "sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==" 587 | }, 588 | "@opentelemetry/api": { 589 | "version": "1.2.0", 590 | "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.2.0.tgz", 591 | "integrity": "sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g==" 592 | }, 593 | "@opentelemetry/core": { 594 | "version": "1.7.0", 595 | "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.7.0.tgz", 596 | "integrity": "sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ==", 597 | "requires": { 598 | "@opentelemetry/semantic-conventions": "1.7.0" 599 | } 600 | }, 601 | "@opentelemetry/resources": { 602 | "version": "1.7.0", 603 | "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.7.0.tgz", 604 | "integrity": "sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg==", 605 | "requires": { 606 | "@opentelemetry/core": "1.7.0", 607 | "@opentelemetry/semantic-conventions": "1.7.0" 608 | } 609 | }, 610 | "@opentelemetry/sdk-trace-base": { 611 | "version": "1.7.0", 612 | "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz", 613 | "integrity": "sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg==", 614 | "requires": { 615 | "@opentelemetry/core": "1.7.0", 616 | "@opentelemetry/resources": "1.7.0", 617 | "@opentelemetry/semantic-conventions": "1.7.0" 618 | } 619 | }, 620 | "@opentelemetry/semantic-conventions": { 621 | "version": "1.7.0", 622 | "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz", 623 | "integrity": "sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA==" 624 | }, 625 | "@types/node": { 626 | "version": "18.8.4", 627 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.4.tgz", 628 | "integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==" 629 | }, 630 | "@types/node-fetch": { 631 | "version": "2.6.2", 632 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", 633 | "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", 634 | "requires": { 635 | "@types/node": "*", 636 | "form-data": "^3.0.0" 637 | }, 638 | "dependencies": { 639 | "form-data": { 640 | "version": "3.0.1", 641 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 642 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 643 | "requires": { 644 | "asynckit": "^0.4.0", 645 | "combined-stream": "^1.0.8", 646 | "mime-types": "^2.1.12" 647 | } 648 | } 649 | } 650 | }, 651 | "@types/tunnel": { 652 | "version": "0.0.3", 653 | "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", 654 | "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", 655 | "requires": { 656 | "@types/node": "*" 657 | } 658 | }, 659 | "applicationinsights": { 660 | "version": "2.3.5", 661 | "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.3.5.tgz", 662 | "integrity": "sha512-QU6EEZbobj9NL2o/XLIDStCMfwrrLwFrbJrDw9ih1wb5bz7v0cwUm6kPXiKtNAAny4hWp9/BtBhtFKvc3tWZ3w==", 663 | "requires": { 664 | "@azure/core-http": "^2.2.3", 665 | "@microsoft/applicationinsights-web-snippet": "^1.0.1", 666 | "@opentelemetry/api": "^1.0.4", 667 | "@opentelemetry/core": "^1.0.1", 668 | "@opentelemetry/sdk-trace-base": "^1.0.1", 669 | "@opentelemetry/semantic-conventions": "^1.0.1", 670 | "cls-hooked": "^4.2.2", 671 | "continuation-local-storage": "^3.2.1", 672 | "diagnostic-channel": "1.1.0", 673 | "diagnostic-channel-publishers": "1.0.5" 674 | } 675 | }, 676 | "async-hook-jl": { 677 | "version": "1.7.6", 678 | "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", 679 | "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", 680 | "requires": { 681 | "stack-chain": "^1.3.7" 682 | } 683 | }, 684 | "async-listener": { 685 | "version": "0.6.10", 686 | "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", 687 | "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", 688 | "requires": { 689 | "semver": "^5.3.0", 690 | "shimmer": "^1.1.0" 691 | } 692 | }, 693 | "asynckit": { 694 | "version": "0.4.0", 695 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 696 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 697 | }, 698 | "cls-hooked": { 699 | "version": "4.2.2", 700 | "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", 701 | "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", 702 | "requires": { 703 | "async-hook-jl": "^1.7.6", 704 | "emitter-listener": "^1.0.1", 705 | "semver": "^5.4.1" 706 | } 707 | }, 708 | "combined-stream": { 709 | "version": "1.0.8", 710 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 711 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 712 | "requires": { 713 | "delayed-stream": "~1.0.0" 714 | } 715 | }, 716 | "continuation-local-storage": { 717 | "version": "3.2.1", 718 | "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", 719 | "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", 720 | "requires": { 721 | "async-listener": "^0.6.0", 722 | "emitter-listener": "^1.1.1" 723 | } 724 | }, 725 | "delayed-stream": { 726 | "version": "1.0.0", 727 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 728 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 729 | }, 730 | "diagnostic-channel": { 731 | "version": "1.1.0", 732 | "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz", 733 | "integrity": "sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ==", 734 | "requires": { 735 | "semver": "^5.3.0" 736 | } 737 | }, 738 | "diagnostic-channel-publishers": { 739 | "version": "1.0.5", 740 | "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz", 741 | "integrity": "sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg==", 742 | "requires": {} 743 | }, 744 | "emitter-listener": { 745 | "version": "1.1.2", 746 | "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", 747 | "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", 748 | "requires": { 749 | "shimmer": "^1.2.0" 750 | } 751 | }, 752 | "form-data": { 753 | "version": "4.0.0", 754 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 755 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 756 | "requires": { 757 | "asynckit": "^0.4.0", 758 | "combined-stream": "^1.0.8", 759 | "mime-types": "^2.1.12" 760 | } 761 | }, 762 | "mime-db": { 763 | "version": "1.52.0", 764 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 765 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 766 | }, 767 | "mime-types": { 768 | "version": "2.1.35", 769 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 770 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 771 | "requires": { 772 | "mime-db": "1.52.0" 773 | } 774 | }, 775 | "node-fetch": { 776 | "version": "2.6.7", 777 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 778 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 779 | "requires": { 780 | "whatwg-url": "^5.0.0" 781 | } 782 | }, 783 | "process": { 784 | "version": "0.11.10", 785 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 786 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" 787 | }, 788 | "psl": { 789 | "version": "1.9.0", 790 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", 791 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" 792 | }, 793 | "punycode": { 794 | "version": "2.1.1", 795 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 796 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 797 | }, 798 | "querystringify": { 799 | "version": "2.2.0", 800 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 801 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 802 | }, 803 | "requires-port": { 804 | "version": "1.0.0", 805 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 806 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 807 | }, 808 | "sax": { 809 | "version": "1.2.4", 810 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 811 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 812 | }, 813 | "semver": { 814 | "version": "5.7.1", 815 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 816 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 817 | }, 818 | "shimmer": { 819 | "version": "1.2.1", 820 | "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", 821 | "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" 822 | }, 823 | "stack-chain": { 824 | "version": "1.3.7", 825 | "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", 826 | "integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==" 827 | }, 828 | "tough-cookie": { 829 | "version": "4.1.2", 830 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", 831 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", 832 | "requires": { 833 | "psl": "^1.1.33", 834 | "punycode": "^2.1.1", 835 | "universalify": "^0.2.0", 836 | "url-parse": "^1.5.3" 837 | } 838 | }, 839 | "tr46": { 840 | "version": "0.0.3", 841 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 842 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 843 | }, 844 | "tslib": { 845 | "version": "2.4.0", 846 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 847 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 848 | }, 849 | "tunnel": { 850 | "version": "0.0.6", 851 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", 852 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" 853 | }, 854 | "universalify": { 855 | "version": "0.2.0", 856 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", 857 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" 858 | }, 859 | "url-parse": { 860 | "version": "1.5.10", 861 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 862 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 863 | "requires": { 864 | "querystringify": "^2.1.1", 865 | "requires-port": "^1.0.0" 866 | } 867 | }, 868 | "uuid": { 869 | "version": "8.3.2", 870 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 871 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 872 | }, 873 | "webidl-conversions": { 874 | "version": "3.0.1", 875 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 876 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 877 | }, 878 | "whatwg-url": { 879 | "version": "5.0.0", 880 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 881 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 882 | "requires": { 883 | "tr46": "~0.0.3", 884 | "webidl-conversions": "^3.0.0" 885 | } 886 | }, 887 | "xml2js": { 888 | "version": "0.4.23", 889 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", 890 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", 891 | "requires": { 892 | "sax": ">=0.6.0", 893 | "xmlbuilder": "~11.0.0" 894 | } 895 | }, 896 | "xmlbuilder": { 897 | "version": "11.0.1", 898 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 899 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" 900 | } 901 | } 902 | } 903 | --------------------------------------------------------------------------------