├── .gitignore ├── LICENSE ├── README.md ├── assets ├── bigquery.png ├── cover.svg └── page_example.png ├── package.json ├── packages ├── client │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── util.ts │ ├── tsconfig.json │ └── yarn.lock └── server │ ├── README.md │ ├── package.json │ ├── src │ ├── index.ts │ └── schemas.ts │ ├── tsconfig.json │ └── yarn.lock └── yarn.lock /.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 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | 119 | 120 | 121 | .DS_Store 122 | .env 123 | .eslintcache 124 | .stylelintcache 125 | .tsbuildinfo 126 | 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Instant Domain Search, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Cover logo](./assets/cover.svg)](https://vitals.dev) 2 | 3 | # [Instant Vitals](https://vitals.dev): Simple tools to track and improve your Web Vitals scores 4 | 5 | [![npm (tag)](https://img.shields.io/npm/v/@instantdomain/vitals-client/latest?label=%40instantdomain%2Fvitals-client)](https://www.npmjs.com/package/@instantdomain/vitals-client) 6 | [![npm (tag)](https://img.shields.io/npm/v/@instantdomain/vitals-server/latest?label=%40instantdomain%2Fvitals-server)](https://www.npmjs.com/package/@instantdomain/vitals-server) 7 | [![GitHub](https://img.shields.io/github/license/instantdomainsearch/instant-vitals)](https://github.com/InstantDomainSearch/instant-vitals/blob/main/LICENSE) 8 | 9 | Instant Vitals is a tool for collecting Web Vitals metrics and storing them in [Google BigQuery](https://cloud.google.com/bigquery). The library has both a client and a server component. The client is responsible for collecting the metrics using [web-vitals](https://github.com/GoogleChrome/web-vitals), which is maintained by the Google Chrome team, and sending the metrics to the endpoint of your choice. In addition to this, the client converts HTML elements into XPath query strings so that you can easily track down where your problems are later. 10 | 11 | The server component is then used to take these metrics and send them to BigQuery. Once the metrics are in BigQuery, you can more easily query for the information you need to improve your Web Vitals scores. 12 | 13 | For now we'll assume you're using `npm` and that you have some sort of build system in place for the client. While the server component is only available for NodeJS at the moment, this library is more of a pattern than a prescription so it shouldn't be too hard to implement this yourself in your language of choice. 14 | 15 | ## Prerequisites 16 | 17 | Instant Vitals relies on Google BigQuery to store and process data. You should create an account before getting started. Be aware that the paid tier is required to use this library; however, even with a large number of users, the cost should still be very low. 18 | 19 | Once you have created an account, you will need to create a service account. The service account will need permission to create datasets and tables as well as insert records into the database. Create a key for this service account and keep it handy. 20 | 21 | ## Client Installation 22 | 23 | To get started, install the client library in your project: 24 | 25 | ```sh 26 | npm i @instantdomain/vitals-client 27 | ``` 28 | 29 | Then, you can initialize the library in your browser code like so: 30 | 31 | ```typescript 32 | import { init } from "@instantdomain/vitals-client"; 33 | 34 | init({ endpoint: "/api/web-vitals" }); 35 | ``` 36 | 37 | Here I'm using `"/api/web-vitals"` as the endpoint. You'll want to change this to an endpoint of your choice on a server that you control. 38 | 39 | ## Server Installation 40 | 41 | First, install the server library in your NodeJS project: 42 | 43 | ```sh 44 | npm i @instantdomain/vitals-server 45 | ``` 46 | 47 | Then, you can initialize the server component. Here is an example request handler for the endpoint `/api/web-vitals` 48 | 49 | ```typescript 50 | import fs from "fs"; 51 | 52 | import { init, streamVitals } from "@instantdomain/vitals-server"; 53 | 54 | // Google libraries require service key as path to file 55 | const GOOGLE_SERVICE_KEY = process.env.GOOGLE_SERVICE_KEY; 56 | process.env.GOOGLE_APPLICATION_CREDENTIALS = "/tmp/goog_creds"; 57 | fs.writeFileSync( 58 | process.env.GOOGLE_APPLICATION_CREDENTIALS, 59 | GOOGLE_SERVICE_KEY 60 | ); 61 | 62 | const DATASET_ID = "web_vitals"; 63 | init({ datasetId: DATASET_ID }).then().catch(console.error); 64 | 65 | // Request handler 66 | export default async (req, res) => { 67 | const body = JSON.parse(req.body); 68 | await streamVitals(body, body.name); 69 | res.status(200).end(); 70 | }; 71 | ``` 72 | 73 | Note that here we are storing our Google Cloud service key as a string in `GOOGLE_SERVICE_KEY`. We then need to write the key to a temporary file before instructing the BigQuery library to read from that file. 74 | 75 | ## Running 76 | 77 | Once you start your server, you should see the requisite dataset and tables created in BigQuery. Once you have some visitors on your site, you should start seeing your Web Vitals metrics data in these tables: 78 | 79 | ![BigQuery screenshot](./assets/bigquery.png) 80 | 81 | ## Identifying opportunities for improvement 82 | 83 | Now you're ready to identify some elements on your page that are causing issues. For example, you can query for CLS violations like so: 84 | 85 | ```sql 86 | SELECT 87 | `vitalsdev.web_vitals.CLS`.Value, 88 | Node 89 | FROM 90 | `vitalsdev.web_vitals.CLS` 91 | JOIN 92 | UNNEST(Entries) AS Entry 93 | JOIN 94 | UNNEST(Entry.Sources) 95 | WHERE 96 | Node != "" 97 | ORDER BY 98 | value 99 | LIMIT 100 | 10 101 | ``` 102 | 103 | This gives us the following result: 104 | 105 | | Value | Node | 106 | | --------------------- | -------------------------------------------------------- | 107 | | 4.6045324800736724E-4 | /html/body/div[1]/main/div/div/div[2]/div/div/blockquote | 108 | | 7.183070668914928E-4 | /html/body/div[1]/header/div/div/header/div | 109 | | 0.031002668277977697 | /html/body/div[1]/footer | 110 | | 0.035830703317463526 | /html/body/div[1]/main/div/div/div[2] | 111 | | 0.035830703317463526 | /html/body/div[1]/footer | 112 | | 0.035830703317463526 | /html/body/div[1]/main/div/div/div[2] | 113 | | 0.035830703317463526 | /html/body/div[1]/main/div/div/div[2] | 114 | | 0.035830703317463526 | /html/body/div[1]/footer | 115 | | 0.035830703317463526 | /html/body/div[1]/footer | 116 | | 0.03988482067913317 | /html/body/div[1]/footer | 117 | 118 | Now, you can browse to your website in Google Chrome and find the offending elements using the following code: 119 | 120 | ```javascript 121 | $x("/html/body/div[1]/main/div/div/div[2]/div/div/blockquote"); 122 | ``` 123 | 124 | ![Page query example](./assets/page_example.png) 125 | 126 | As you can see, you can identify which elements on the page are degrading your CLS scores. Now you can use this information to improve your users' experiences and your Web Vitals scores! 127 | -------------------------------------------------------------------------------- /assets/bigquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instant-labs/instant-vitals/60e5b57e39bfeb658711bb121f668b853bb5bb7d/assets/bigquery.png -------------------------------------------------------------------------------- /assets/cover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | -------------------------------------------------------------------------------- /assets/page_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instant-labs/instant-vitals/60e5b57e39bfeb658711bb121f668b853bb5bb7d/assets/page_example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instant-vitals", 3 | "version": "1.0.0", 4 | "description": "Simple tools to track and improve your Web Vitals scores", 5 | "main": "index.js", 6 | "repository": "https://github.com/InstantDomainSearch/instant-vitals", 7 | "homepage": "https://vitals.dev", 8 | "license": "MIT", 9 | "private": false, 10 | "keywords": [ 11 | "web vitals", 12 | "LCP", 13 | "FID", 14 | "CLS", 15 | "Largest Contentful Paint", 16 | "First Input Delay", 17 | "Cumulative Layout Shift" 18 | ], 19 | "files": [ 20 | "README.md" 21 | ], 22 | "devDependencies": { 23 | "tslib": "^2.3.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/client/README.md: -------------------------------------------------------------------------------- 1 | # Instant Vitals Client 2 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@instantdomain/vitals-client", 3 | "version": "0.1.4", 4 | "types": "dist/index.d.ts", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "files": [ 8 | "/dist" 9 | ], 10 | "dependencies": { 11 | "web-vitals": "^2.1.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/client/src/index.ts: -------------------------------------------------------------------------------- 1 | import { getCLS, getFID, getLCP, Metric } from "web-vitals"; 2 | import { getXPath, isElement, isNode } from "./util"; 3 | 4 | type options = { 5 | endpoint: string; 6 | }; 7 | 8 | const report = async (metric: Metric, options: options) => { 9 | const cleanedMetric = convertNodesToXPath( 10 | (metric as unknown) as Record 11 | ); 12 | await fetch(options.endpoint, { 13 | body: JSON.stringify(cleanedMetric), 14 | method: "POST", 15 | }); 16 | }; 17 | 18 | function convertNodesToXPath(obj: Record): string { 19 | const getOmittedReplacer = () => { 20 | return (_: string, value: unknown) => { 21 | if (isElement(value) || isNode(value)) { 22 | return getXPath(value as Element); 23 | } 24 | return value; 25 | }; 26 | }; 27 | 28 | return JSON.parse(JSON.stringify(obj, getOmittedReplacer())); 29 | } 30 | 31 | export const init = ({ endpoint }: options) => { 32 | const start = (metric: Metric) => { 33 | report(metric, { endpoint }); 34 | }; 35 | 36 | getCLS(start); 37 | getFID(start); 38 | getLCP(start); 39 | }; 40 | -------------------------------------------------------------------------------- /packages/client/src/util.ts: -------------------------------------------------------------------------------- 1 | export const isBrowser = typeof window !== "undefined"; 2 | export const isElement = (value: unknown): boolean => value instanceof Element; 3 | export const isNode = (value: unknown): boolean => value instanceof Node; 4 | 5 | export const getXPath = (el: Element): string => { 6 | let nodeElem = el; 7 | if (nodeElem && nodeElem.id) { 8 | return '//*[@id="' + nodeElem.id + '"]'; 9 | } 10 | const parts: string[] = []; 11 | while (nodeElem && Node.ELEMENT_NODE === nodeElem.nodeType) { 12 | let nbOfPreviousSiblings = 0; 13 | let hasNextSiblings = false; 14 | let sibling = nodeElem.previousSibling; 15 | while (sibling) { 16 | if ( 17 | sibling.nodeType !== Node.DOCUMENT_TYPE_NODE && 18 | sibling.nodeName === nodeElem.nodeName 19 | ) { 20 | nbOfPreviousSiblings++; 21 | } 22 | sibling = sibling.previousSibling; 23 | } 24 | sibling = nodeElem.nextSibling; 25 | while (sibling) { 26 | if (sibling.nodeName === nodeElem.nodeName) { 27 | hasNextSiblings = true; 28 | break; 29 | } 30 | sibling = sibling.nextSibling; 31 | } 32 | const prefix = nodeElem.prefix ? nodeElem.prefix + ":" : ""; 33 | const nth = 34 | nbOfPreviousSiblings || hasNextSiblings 35 | ? "[" + (nbOfPreviousSiblings + 1) + "]" 36 | : ""; 37 | parts.push(prefix + nodeElem.localName + nth); 38 | nodeElem = nodeElem.parentNode as Element; 39 | } 40 | return parts.length ? "/" + parts.reverse().join("/") : ""; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "sourceMap": true, 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "downlevelIteration": true, 14 | "moduleResolution": "node", 15 | "jsx": "preserve", 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "isolatedModules": true, 20 | }, 21 | "include": ["src", "types"], 22 | "exclude": ["node_modules"] 23 | } -------------------------------------------------------------------------------- /packages/client/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | web-vitals@^2.1.2: 6 | version "2.1.2" 7 | resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.2.tgz#3a6c8faebf9097a6ccd17f5f45c9485d8d62dab1" 8 | integrity sha512-nZnEH8dj+vJFqCRYdvYv0a59iLXsb8jJkt+xvXfwgnkyPdsSLtKNlYmtTDiHmTNGXeSXtpjTTUcNvFtrAk6VMQ== 9 | -------------------------------------------------------------------------------- /packages/server/README.md: -------------------------------------------------------------------------------- 1 | # instant-vitals 2 | -------------------------------------------------------------------------------- /packages/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@instantdomain/vitals-server", 3 | "version": "0.1.3", 4 | "types": "dist/index.d.ts", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "files": [ 8 | "/dist" 9 | ], 10 | "dependencies": { 11 | "@google-cloud/bigquery": "^5.5.0", 12 | "@types/node": "^16.11.6" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^14.14.41" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/server/src/index.ts: -------------------------------------------------------------------------------- 1 | import { schemas } from "./schemas"; 2 | import { BigQuery } from "@google-cloud/bigquery"; 3 | 4 | interface Options { 5 | datasetId: string; 6 | } 7 | 8 | const options: Options = { 9 | datasetId: "", 10 | }; 11 | 12 | export const streamVitals = async (metric: unknown, table: string) => { 13 | const bigquery = new BigQuery(); 14 | try { 15 | await bigquery.dataset(options.datasetId).table(table).insert([metric]); 16 | } catch (e) { 17 | console.error(e); 18 | } 19 | }; 20 | 21 | export const init = async ({ datasetId }: Options) => { 22 | options.datasetId = datasetId; 23 | 24 | const bigquery = new BigQuery(); 25 | 26 | const [datasets] = await bigquery.getDatasets(); 27 | if (!datasets.map((set) => set.id).includes(datasetId)) { 28 | await bigquery.createDataset(datasetId); 29 | } 30 | 31 | for (const [metricName, schema] of Object.entries(schemas)) { 32 | try { 33 | await bigquery 34 | .dataset(options.datasetId) 35 | .createTable(metricName, { schema: schema }); 36 | } catch (e) {} 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /packages/server/src/schemas.ts: -------------------------------------------------------------------------------- 1 | export const schemas = { 2 | CLS: { 3 | fields: [ 4 | { 5 | mode: "REQUIRED", 6 | name: "Name", 7 | type: "STRING", 8 | }, 9 | { 10 | mode: "REQUIRED", 11 | name: "Value", 12 | type: "FLOAT", 13 | }, 14 | { 15 | mode: "REQUIRED", 16 | name: "Delta", 17 | type: "FLOAT", 18 | }, 19 | { 20 | fields: [ 21 | { 22 | mode: "REQUIRED", 23 | name: "Name", 24 | type: "STRING", 25 | }, 26 | { 27 | mode: "REQUIRED", 28 | name: "EntryType", 29 | type: "STRING", 30 | }, 31 | { 32 | mode: "REQUIRED", 33 | name: "StartTime", 34 | type: "FLOAT", 35 | }, 36 | { 37 | mode: "REQUIRED", 38 | name: "Duration", 39 | type: "INTEGER", 40 | }, 41 | { 42 | mode: "REQUIRED", 43 | name: "Value", 44 | type: "FLOAT", 45 | }, 46 | { 47 | mode: "REQUIRED", 48 | name: "HadRecentInput", 49 | type: "BOOLEAN", 50 | }, 51 | { 52 | mode: "REQUIRED", 53 | name: "LastInputTime", 54 | type: "FLOAT", 55 | }, 56 | { 57 | fields: [ 58 | { 59 | mode: "REQUIRED", 60 | name: "Node", 61 | type: "STRING", 62 | }, 63 | { 64 | fields: [ 65 | { 66 | mode: "REQUIRED", 67 | name: "X", 68 | type: "INTEGER", 69 | }, 70 | { 71 | mode: "REQUIRED", 72 | name: "Y", 73 | type: "INTEGER", 74 | }, 75 | { 76 | mode: "REQUIRED", 77 | name: "Width", 78 | type: "INTEGER", 79 | }, 80 | { 81 | mode: "REQUIRED", 82 | name: "Height", 83 | type: "INTEGER", 84 | }, 85 | { 86 | mode: "REQUIRED", 87 | name: "Top", 88 | type: "INTEGER", 89 | }, 90 | { 91 | mode: "REQUIRED", 92 | name: "Right", 93 | type: "INTEGER", 94 | }, 95 | { 96 | mode: "REQUIRED", 97 | name: "Bottom", 98 | type: "INTEGER", 99 | }, 100 | { 101 | mode: "REQUIRED", 102 | name: "Left", 103 | type: "INTEGER", 104 | }, 105 | ], 106 | mode: "REQUIRED", 107 | name: "PreviousRect", 108 | type: "RECORD", 109 | }, 110 | { 111 | fields: [ 112 | { 113 | mode: "REQUIRED", 114 | name: "X", 115 | type: "INTEGER", 116 | }, 117 | { 118 | mode: "REQUIRED", 119 | name: "Y", 120 | type: "INTEGER", 121 | }, 122 | { 123 | mode: "REQUIRED", 124 | name: "Width", 125 | type: "INTEGER", 126 | }, 127 | { 128 | mode: "REQUIRED", 129 | name: "Height", 130 | type: "INTEGER", 131 | }, 132 | { 133 | mode: "REQUIRED", 134 | name: "Top", 135 | type: "INTEGER", 136 | }, 137 | { 138 | mode: "REQUIRED", 139 | name: "Right", 140 | type: "INTEGER", 141 | }, 142 | { 143 | mode: "REQUIRED", 144 | name: "Bottom", 145 | type: "INTEGER", 146 | }, 147 | { 148 | mode: "REQUIRED", 149 | name: "Left", 150 | type: "INTEGER", 151 | }, 152 | ], 153 | mode: "REQUIRED", 154 | name: "CurrentRect", 155 | type: "RECORD", 156 | }, 157 | ], 158 | mode: "REPEATED", 159 | name: "Sources", 160 | type: "RECORD", 161 | }, 162 | ], 163 | mode: "REPEATED", 164 | name: "Entries", 165 | type: "RECORD", 166 | }, 167 | { 168 | mode: "REQUIRED", 169 | name: "ID", 170 | type: "STRING", 171 | }, 172 | ], 173 | }, 174 | FID: { 175 | fields: [ 176 | { 177 | mode: "REQUIRED", 178 | name: "Name", 179 | type: "STRING", 180 | }, 181 | { 182 | mode: "REQUIRED", 183 | name: "Value", 184 | type: "FLOAT", 185 | }, 186 | { 187 | mode: "REQUIRED", 188 | name: "Delta", 189 | type: "FLOAT", 190 | }, 191 | { 192 | fields: [ 193 | { 194 | mode: "REQUIRED", 195 | name: "Name", 196 | type: "STRING", 197 | }, 198 | { 199 | mode: "REQUIRED", 200 | name: "EntryType", 201 | type: "STRING", 202 | }, 203 | { 204 | mode: "REQUIRED", 205 | name: "StartTime", 206 | type: "FLOAT", 207 | }, 208 | { 209 | mode: "REQUIRED", 210 | name: "Duration", 211 | type: "INTEGER", 212 | }, 213 | { 214 | mode: "REQUIRED", 215 | name: "ProcessingStart", 216 | type: "FLOAT", 217 | }, 218 | { 219 | mode: "REQUIRED", 220 | name: "ProcessingEnd", 221 | type: "FLOAT", 222 | }, 223 | { 224 | mode: "REQUIRED", 225 | name: "Cancelable", 226 | type: "BOOLEAN", 227 | }, 228 | ], 229 | mode: "REPEATED", 230 | name: "Entries", 231 | type: "RECORD", 232 | }, 233 | { 234 | mode: "REQUIRED", 235 | name: "ID", 236 | type: "STRING", 237 | }, 238 | ], 239 | }, 240 | FCP: { 241 | fields: [ 242 | { 243 | mode: "REQUIRED", 244 | name: "Name", 245 | type: "STRING", 246 | }, 247 | { 248 | mode: "REQUIRED", 249 | name: "Value", 250 | type: "FLOAT", 251 | }, 252 | { 253 | mode: "REQUIRED", 254 | name: "Delta", 255 | type: "FLOAT", 256 | }, 257 | { 258 | fields: [ 259 | { 260 | mode: "REQUIRED", 261 | name: "Name", 262 | type: "STRING", 263 | }, 264 | { 265 | mode: "REQUIRED", 266 | name: "EntryType", 267 | type: "STRING", 268 | }, 269 | { 270 | mode: "REQUIRED", 271 | name: "StartTime", 272 | type: "FLOAT", 273 | }, 274 | { 275 | mode: "REQUIRED", 276 | name: "Duration", 277 | type: "INTEGER", 278 | }, 279 | ], 280 | mode: "REPEATED", 281 | name: "Entries", 282 | type: "RECORD", 283 | }, 284 | { 285 | mode: "REQUIRED", 286 | name: "ID", 287 | type: "STRING", 288 | }, 289 | ], 290 | }, 291 | LCP: { 292 | fields: [ 293 | { 294 | mode: "REQUIRED", 295 | name: "Name", 296 | type: "STRING", 297 | }, 298 | { 299 | mode: "REQUIRED", 300 | name: "Value", 301 | type: "FLOAT", 302 | }, 303 | { 304 | mode: "REQUIRED", 305 | name: "Delta", 306 | type: "FLOAT", 307 | }, 308 | { 309 | fields: [ 310 | { 311 | mode: "REQUIRED", 312 | name: "Name", 313 | type: "STRING", 314 | }, 315 | { 316 | mode: "REQUIRED", 317 | name: "EntryType", 318 | type: "STRING", 319 | }, 320 | { 321 | mode: "REQUIRED", 322 | name: "StartTime", 323 | type: "FLOAT", 324 | }, 325 | { 326 | mode: "REQUIRED", 327 | name: "Duration", 328 | type: "INTEGER", 329 | }, 330 | { 331 | mode: "REQUIRED", 332 | name: "Size", 333 | type: "INTEGER", 334 | }, 335 | { 336 | mode: "REQUIRED", 337 | name: "RenderTime", 338 | type: "FLOAT", 339 | }, 340 | { 341 | mode: "REQUIRED", 342 | name: "LoadTime", 343 | type: "INTEGER", 344 | }, 345 | { 346 | mode: "REQUIRED", 347 | name: "ID", 348 | type: "STRING", 349 | }, 350 | { 351 | mode: "REQUIRED", 352 | name: "URL", 353 | type: "STRING", 354 | }, 355 | { 356 | mode: "REQUIRED", 357 | name: "Element", 358 | type: "STRING", 359 | }, 360 | ], 361 | mode: "REPEATED", 362 | name: "Entries", 363 | type: "RECORD", 364 | }, 365 | { 366 | mode: "REQUIRED", 367 | name: "ID", 368 | type: "STRING", 369 | }, 370 | ], 371 | }, 372 | TTFB: { 373 | fields: [ 374 | { 375 | mode: "REQUIRED", 376 | name: "Name", 377 | type: "STRING", 378 | }, 379 | { 380 | mode: "REQUIRED", 381 | name: "Value", 382 | type: "FLOAT", 383 | }, 384 | { 385 | mode: "REQUIRED", 386 | name: "Delta", 387 | type: "FLOAT", 388 | }, 389 | { 390 | fields: [ 391 | { 392 | mode: "REQUIRED", 393 | name: "Name", 394 | type: "STRING", 395 | }, 396 | { 397 | mode: "REQUIRED", 398 | name: "EntryType", 399 | type: "STRING", 400 | }, 401 | { 402 | mode: "REQUIRED", 403 | name: "StartTime", 404 | type: "INTEGER", 405 | }, 406 | { 407 | mode: "REQUIRED", 408 | name: "Duration", 409 | type: "FLOAT", 410 | }, 411 | { 412 | mode: "REQUIRED", 413 | name: "InitiatorType", 414 | type: "STRING", 415 | }, 416 | { 417 | mode: "REQUIRED", 418 | name: "NextHopProtocol", 419 | type: "STRING", 420 | }, 421 | { 422 | mode: "REQUIRED", 423 | name: "WorkerStart", 424 | type: "INTEGER", 425 | }, 426 | { 427 | mode: "REQUIRED", 428 | name: "RedirectStart", 429 | type: "INTEGER", 430 | }, 431 | { 432 | mode: "REQUIRED", 433 | name: "RedirectEnd", 434 | type: "INTEGER", 435 | }, 436 | { 437 | mode: "REQUIRED", 438 | name: "FetchStart", 439 | type: "FLOAT", 440 | }, 441 | { 442 | mode: "REQUIRED", 443 | name: "DomainLookupStart", 444 | type: "FLOAT", 445 | }, 446 | { 447 | mode: "REQUIRED", 448 | name: "DomainLookupEnd", 449 | type: "FLOAT", 450 | }, 451 | { 452 | mode: "REQUIRED", 453 | name: "ConnectStart", 454 | type: "FLOAT", 455 | }, 456 | { 457 | mode: "REQUIRED", 458 | name: "ConnectEnd", 459 | type: "FLOAT", 460 | }, 461 | { 462 | mode: "REQUIRED", 463 | name: "SecureConnectionStart", 464 | type: "FLOAT", 465 | }, 466 | { 467 | mode: "REQUIRED", 468 | name: "RequestStart", 469 | type: "FLOAT", 470 | }, 471 | { 472 | mode: "REQUIRED", 473 | name: "ResponseStart", 474 | type: "FLOAT", 475 | }, 476 | { 477 | mode: "REQUIRED", 478 | name: "ResponseEnd", 479 | type: "FLOAT", 480 | }, 481 | { 482 | mode: "REQUIRED", 483 | name: "TransferSize", 484 | type: "INTEGER", 485 | }, 486 | { 487 | mode: "REQUIRED", 488 | name: "EncodedBodySize", 489 | type: "INTEGER", 490 | }, 491 | { 492 | mode: "REQUIRED", 493 | name: "DecodedBodySize", 494 | type: "INTEGER", 495 | }, 496 | { 497 | mode: "REQUIRED", 498 | name: "UnloadEventStart", 499 | type: "FLOAT", 500 | }, 501 | { 502 | mode: "REQUIRED", 503 | name: "UnloadEventEnd", 504 | type: "FLOAT", 505 | }, 506 | { 507 | mode: "REQUIRED", 508 | name: "DomInteractive", 509 | type: "FLOAT", 510 | }, 511 | { 512 | mode: "REQUIRED", 513 | name: "DomContentLoadedEventStart", 514 | type: "FLOAT", 515 | }, 516 | { 517 | mode: "REQUIRED", 518 | name: "DomContentLoadedEventEnd", 519 | type: "FLOAT", 520 | }, 521 | { 522 | mode: "REQUIRED", 523 | name: "DomComplete", 524 | type: "FLOAT", 525 | }, 526 | { 527 | mode: "REQUIRED", 528 | name: "LoadEventStart", 529 | type: "FLOAT", 530 | }, 531 | { 532 | mode: "REQUIRED", 533 | name: "LoadEventEnd", 534 | type: "FLOAT", 535 | }, 536 | { 537 | mode: "REQUIRED", 538 | name: "Type", 539 | type: "STRING", 540 | }, 541 | { 542 | mode: "REQUIRED", 543 | name: "RedirectCount", 544 | type: "INTEGER", 545 | }, 546 | ], 547 | mode: "REPEATED", 548 | name: "Entries", 549 | type: "RECORD", 550 | }, 551 | { 552 | mode: "REQUIRED", 553 | name: "ID", 554 | type: "STRING", 555 | }, 556 | ], 557 | }, 558 | }; 559 | -------------------------------------------------------------------------------- /packages/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "lib": ["ESNext", "DOM"], 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "downlevelIteration": true, 16 | "moduleResolution": "node", 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "isolatedModules": true, 21 | }, 22 | "include": ["src", "types"], 23 | "exclude": ["node_modules"] 24 | } -------------------------------------------------------------------------------- /packages/server/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@google-cloud/bigquery@^5.5.0": 6 | version "5.9.1" 7 | resolved "https://registry.yarnpkg.com/@google-cloud/bigquery/-/bigquery-5.9.1.tgz#96cee86fa0caef4a7e1470efde9295bc09f5981f" 8 | integrity sha512-80pMzhAC299CSiXW9TvR8AARLaPRDeQg8pSAvrVcLXcUkx1hWvVx2m94nBZ4KUoZb4LVWIHHYhvFB6XvIcxqjw== 9 | dependencies: 10 | "@google-cloud/common" "^3.1.0" 11 | "@google-cloud/paginator" "^3.0.0" 12 | "@google-cloud/promisify" "^2.0.0" 13 | arrify "^2.0.1" 14 | big.js "^6.0.0" 15 | duplexify "^4.0.0" 16 | extend "^3.0.2" 17 | is "^3.3.0" 18 | p-event "^4.1.0" 19 | stream-events "^1.0.5" 20 | uuid "^8.0.0" 21 | 22 | "@google-cloud/common@^3.1.0": 23 | version "3.8.1" 24 | resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-3.8.1.tgz#1313c55bb66df88f69bf7c828135fae25fbd2036" 25 | integrity sha512-FOs3NFU6bDt5mXE7IFpwIeqzLwRZNu9lJYl+bHVNkwmxX/w4VyDZAiGjQHhpV1Ek+muNKlX8HPchxaIxNTuOhw== 26 | dependencies: 27 | "@google-cloud/projectify" "^2.0.0" 28 | "@google-cloud/promisify" "^2.0.0" 29 | arrify "^2.0.1" 30 | duplexify "^4.1.1" 31 | ent "^2.2.0" 32 | extend "^3.0.2" 33 | google-auth-library "^7.9.2" 34 | retry-request "^4.2.2" 35 | teeny-request "^7.0.0" 36 | 37 | "@google-cloud/paginator@^3.0.0": 38 | version "3.0.6" 39 | resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-3.0.6.tgz#02a59dccd348d515069779a4f77a4a4fd15594da" 40 | integrity sha512-XCTm/GfQIlc1ZxpNtTSs/mnZxC2cePNhxU3X8EzHXKIJ2JFncmJj2Fcd2IP+gbmZaSZnY0juFxbUCkIeuu/2eQ== 41 | dependencies: 42 | arrify "^2.0.0" 43 | extend "^3.0.2" 44 | 45 | "@google-cloud/projectify@^2.0.0": 46 | version "2.1.1" 47 | resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-2.1.1.tgz#ae6af4fee02d78d044ae434699a630f8df0084ef" 48 | integrity sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ== 49 | 50 | "@google-cloud/promisify@^2.0.0": 51 | version "2.0.4" 52 | resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-2.0.4.tgz#9d8705ecb2baa41b6b2673f3a8e9b7b7e1abc52a" 53 | integrity sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA== 54 | 55 | "@tootallnate/once@2": 56 | version "2.0.0" 57 | resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" 58 | integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== 59 | 60 | "@types/node@^16.11.6": 61 | version "16.11.6" 62 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" 63 | integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== 64 | 65 | abort-controller@^3.0.0: 66 | version "3.0.0" 67 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" 68 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== 69 | dependencies: 70 | event-target-shim "^5.0.0" 71 | 72 | agent-base@6: 73 | version "6.0.2" 74 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 75 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 76 | dependencies: 77 | debug "4" 78 | 79 | arrify@^2.0.0, arrify@^2.0.1: 80 | version "2.0.1" 81 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" 82 | integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== 83 | 84 | base64-js@^1.3.0: 85 | version "1.5.1" 86 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 87 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 88 | 89 | big.js@^6.0.0: 90 | version "6.1.1" 91 | resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.1.1.tgz#63b35b19dc9775c94991ee5db7694880655d5537" 92 | integrity sha512-1vObw81a8ylZO5ePrtMay0n018TcftpTA5HFKDaSuiUDBo8biRBtjIobw60OpwuvrGk+FsxKamqN4cnmj/eXdg== 93 | 94 | bignumber.js@^9.0.0: 95 | version "9.0.1" 96 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" 97 | integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== 98 | 99 | buffer-equal-constant-time@1.0.1: 100 | version "1.0.1" 101 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 102 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 103 | 104 | debug@4, debug@^4.1.1: 105 | version "4.3.2" 106 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" 107 | integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== 108 | dependencies: 109 | ms "2.1.2" 110 | 111 | duplexify@^4.0.0, duplexify@^4.1.1: 112 | version "4.1.2" 113 | resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" 114 | integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== 115 | dependencies: 116 | end-of-stream "^1.4.1" 117 | inherits "^2.0.3" 118 | readable-stream "^3.1.1" 119 | stream-shift "^1.0.0" 120 | 121 | ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: 122 | version "1.0.11" 123 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 124 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 125 | dependencies: 126 | safe-buffer "^5.0.1" 127 | 128 | end-of-stream@^1.4.1: 129 | version "1.4.4" 130 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 131 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 132 | dependencies: 133 | once "^1.4.0" 134 | 135 | ent@^2.2.0: 136 | version "2.2.0" 137 | resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" 138 | integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= 139 | 140 | event-target-shim@^5.0.0: 141 | version "5.0.1" 142 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 143 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 144 | 145 | extend@^3.0.2: 146 | version "3.0.2" 147 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 148 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 149 | 150 | fast-text-encoding@^1.0.0: 151 | version "1.0.3" 152 | resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" 153 | integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== 154 | 155 | gaxios@^4.0.0: 156 | version "4.3.2" 157 | resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.3.2.tgz#845827c2dc25a0213c8ab4155c7a28910f5be83f" 158 | integrity sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q== 159 | dependencies: 160 | abort-controller "^3.0.0" 161 | extend "^3.0.2" 162 | https-proxy-agent "^5.0.0" 163 | is-stream "^2.0.0" 164 | node-fetch "^2.6.1" 165 | 166 | gcp-metadata@^4.2.0: 167 | version "4.3.1" 168 | resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.3.1.tgz#fb205fe6a90fef2fd9c85e6ba06e5559ee1eefa9" 169 | integrity sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A== 170 | dependencies: 171 | gaxios "^4.0.0" 172 | json-bigint "^1.0.0" 173 | 174 | google-auth-library@^7.9.2: 175 | version "7.10.2" 176 | resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-7.10.2.tgz#7e48176f50e725e1d65b6a838ec9e9464e6ba689" 177 | integrity sha512-M37o9Kxa/TLvOLgF71SXvLeVEP5sbSTmKl1zlIgl72SFy5PtsU3pOdu8G8MIHHpQ3/NZabDI8rQkA9DvQVKkPA== 178 | dependencies: 179 | arrify "^2.0.0" 180 | base64-js "^1.3.0" 181 | ecdsa-sig-formatter "^1.0.11" 182 | fast-text-encoding "^1.0.0" 183 | gaxios "^4.0.0" 184 | gcp-metadata "^4.2.0" 185 | gtoken "^5.0.4" 186 | jws "^4.0.0" 187 | lru-cache "^6.0.0" 188 | 189 | google-p12-pem@^3.0.3: 190 | version "3.1.2" 191 | resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.1.2.tgz#c3d61c2da8e10843ff830fdb0d2059046238c1d4" 192 | integrity sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A== 193 | dependencies: 194 | node-forge "^0.10.0" 195 | 196 | gtoken@^5.0.4: 197 | version "5.3.1" 198 | resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.1.tgz#c1c2598a826f2b5df7c6bb53d7be6cf6d50c3c78" 199 | integrity sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ== 200 | dependencies: 201 | gaxios "^4.0.0" 202 | google-p12-pem "^3.0.3" 203 | jws "^4.0.0" 204 | 205 | http-proxy-agent@^5.0.0: 206 | version "5.0.0" 207 | resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" 208 | integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== 209 | dependencies: 210 | "@tootallnate/once" "2" 211 | agent-base "6" 212 | debug "4" 213 | 214 | https-proxy-agent@^5.0.0: 215 | version "5.0.0" 216 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" 217 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== 218 | dependencies: 219 | agent-base "6" 220 | debug "4" 221 | 222 | inherits@^2.0.3: 223 | version "2.0.4" 224 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 225 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 226 | 227 | is-stream@^2.0.0: 228 | version "2.0.1" 229 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" 230 | integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== 231 | 232 | is@^3.3.0: 233 | version "3.3.0" 234 | resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" 235 | integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== 236 | 237 | json-bigint@^1.0.0: 238 | version "1.0.0" 239 | resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" 240 | integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== 241 | dependencies: 242 | bignumber.js "^9.0.0" 243 | 244 | jwa@^2.0.0: 245 | version "2.0.0" 246 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" 247 | integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== 248 | dependencies: 249 | buffer-equal-constant-time "1.0.1" 250 | ecdsa-sig-formatter "1.0.11" 251 | safe-buffer "^5.0.1" 252 | 253 | jws@^4.0.0: 254 | version "4.0.0" 255 | resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" 256 | integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== 257 | dependencies: 258 | jwa "^2.0.0" 259 | safe-buffer "^5.0.1" 260 | 261 | lru-cache@^6.0.0: 262 | version "6.0.0" 263 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 264 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 265 | dependencies: 266 | yallist "^4.0.0" 267 | 268 | ms@2.1.2: 269 | version "2.1.2" 270 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 271 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 272 | 273 | node-fetch@^2.6.1: 274 | version "2.6.6" 275 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" 276 | integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== 277 | dependencies: 278 | whatwg-url "^5.0.0" 279 | 280 | node-forge@^0.10.0: 281 | version "0.10.0" 282 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" 283 | integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== 284 | 285 | once@^1.4.0: 286 | version "1.4.0" 287 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 288 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 289 | dependencies: 290 | wrappy "1" 291 | 292 | p-event@^4.1.0: 293 | version "4.2.0" 294 | resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" 295 | integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== 296 | dependencies: 297 | p-timeout "^3.1.0" 298 | 299 | p-finally@^1.0.0: 300 | version "1.0.0" 301 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 302 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= 303 | 304 | p-timeout@^3.1.0: 305 | version "3.2.0" 306 | resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" 307 | integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== 308 | dependencies: 309 | p-finally "^1.0.0" 310 | 311 | readable-stream@^3.1.1: 312 | version "3.6.0" 313 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 314 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 315 | dependencies: 316 | inherits "^2.0.3" 317 | string_decoder "^1.1.1" 318 | util-deprecate "^1.0.1" 319 | 320 | retry-request@^4.2.2: 321 | version "4.2.2" 322 | resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.2.2.tgz#b7d82210b6d2651ed249ba3497f07ea602f1a903" 323 | integrity sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg== 324 | dependencies: 325 | debug "^4.1.1" 326 | extend "^3.0.2" 327 | 328 | safe-buffer@^5.0.1, safe-buffer@~5.2.0: 329 | version "5.2.1" 330 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 331 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 332 | 333 | stream-events@^1.0.5: 334 | version "1.0.5" 335 | resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" 336 | integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== 337 | dependencies: 338 | stubs "^3.0.0" 339 | 340 | stream-shift@^1.0.0: 341 | version "1.0.1" 342 | resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" 343 | integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== 344 | 345 | string_decoder@^1.1.1: 346 | version "1.3.0" 347 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 348 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 349 | dependencies: 350 | safe-buffer "~5.2.0" 351 | 352 | stubs@^3.0.0: 353 | version "3.0.0" 354 | resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" 355 | integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= 356 | 357 | teeny-request@^7.0.0: 358 | version "7.1.3" 359 | resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.1.3.tgz#5a3d90c559a6c664a993477b138e331a518765ba" 360 | integrity sha512-Ew3aoFzgQEatLA5OBIjdr1DWJUaC1xardG+qbPPo5k/y/3fMwXLxpjh5UB5dVfElktLaQbbMs80chkz53ByvSg== 361 | dependencies: 362 | http-proxy-agent "^5.0.0" 363 | https-proxy-agent "^5.0.0" 364 | node-fetch "^2.6.1" 365 | stream-events "^1.0.5" 366 | uuid "^8.0.0" 367 | 368 | tr46@~0.0.3: 369 | version "0.0.3" 370 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 371 | integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= 372 | 373 | util-deprecate@^1.0.1: 374 | version "1.0.2" 375 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 376 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 377 | 378 | uuid@^8.0.0: 379 | version "8.3.2" 380 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" 381 | integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== 382 | 383 | webidl-conversions@^3.0.0: 384 | version "3.0.1" 385 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 386 | integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= 387 | 388 | whatwg-url@^5.0.0: 389 | version "5.0.0" 390 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 391 | integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= 392 | dependencies: 393 | tr46 "~0.0.3" 394 | webidl-conversions "^3.0.0" 395 | 396 | wrappy@1: 397 | version "1.0.2" 398 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 399 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 400 | 401 | yallist@^4.0.0: 402 | version "4.0.0" 403 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 404 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 405 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | tslib@^2.3.1: 6 | version "2.3.1" 7 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" 8 | integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== 9 | --------------------------------------------------------------------------------