crux-api 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +822 -70
- package/package.json +23 -34
- package/src/index.js +55 -10
- package/types/index.d.ts +176 -0
- package/dist/batch/src/index.d.ts +0 -17
- package/dist/crux-api.d.ts +0 -100
- package/dist/crux-api.js +0 -1
- package/dist/keys.d.ts +0 -7
- package/dist/keys.js +0 -21
- package/dist/retry.d.ts +0 -8
- package/dist/retry.js +0 -66
- package/dist/script/batch-limits.d.ts +0 -1
- package/dist/script/batch.d.ts +0 -1
- package/dist/script/queryRecord.d.ts +0 -1
- package/dist/src/index.d.ts +0 -100
- package/dist/src/retry.d.ts +0 -8
- package/dist/test/batch.d.ts +0 -1
- package/dist/test/index.d.ts +0 -1
package/package.json
CHANGED
|
@@ -1,45 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crux-api",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "A tiny CrUX API wrapper that supports record & history API, handles errors, and provides types.",
|
|
5
5
|
"repository": "https://github.com/treosh/crux-api",
|
|
6
6
|
"bugs": "https://github.com/treosh/crux-api/issues",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
8
9
|
"source": "src/index.js",
|
|
9
|
-
"
|
|
10
|
-
"types": "dist/crux-api.d.ts",
|
|
11
|
-
"main": "dist/crux-api.js",
|
|
10
|
+
"types": "types/index.d.ts",
|
|
12
11
|
"files": [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
12
|
+
"src",
|
|
13
|
+
"types"
|
|
15
14
|
],
|
|
16
15
|
"scripts": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"build:src-ts": "tsc --declaration --noEmit false --outDir dist/ --allowJs src/index.js && rm dist/index.js && mv dist/index.d.ts dist/crux-api.d.ts",
|
|
20
|
-
"test": "ava -v && prettier -c src test script README.md && yarn build && tsc -p . && size-limit",
|
|
21
|
-
"prepack": "yarn build"
|
|
16
|
+
"types": "tsc --declaration --emitDeclarationOnly --noEmit false --outDir types/ --allowJs src/index.js",
|
|
17
|
+
"test": "yarn types && ava -v test/index.js && prettier -c src test script README.md && tsc -p . && size-limit"
|
|
22
18
|
},
|
|
23
19
|
"devDependencies": {
|
|
24
|
-
"@size-limit/preset-small-lib": "^
|
|
25
|
-
"@types/node
|
|
26
|
-
"ava": "^3.
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"prettier": "^2.3.1",
|
|
32
|
-
"size-limit": "^4.11.0",
|
|
33
|
-
"typescript": "^4.3.2"
|
|
20
|
+
"@size-limit/preset-small-lib": "^9.0.0",
|
|
21
|
+
"@types/node": "^20.8.6",
|
|
22
|
+
"ava": "^5.3.1",
|
|
23
|
+
"node-fetch": "^3.3.2",
|
|
24
|
+
"prettier": "^3.0.3",
|
|
25
|
+
"size-limit": "^9.0.0",
|
|
26
|
+
"typescript": "^5.2.2"
|
|
34
27
|
},
|
|
35
28
|
"keywords": [
|
|
36
29
|
"CrUX",
|
|
37
30
|
"CrUX API",
|
|
31
|
+
"CrUX History API",
|
|
38
32
|
"Chrome User Experience Report",
|
|
39
33
|
"Chrome UX Report",
|
|
40
34
|
"Core Web Vitals",
|
|
41
35
|
"Web Performance",
|
|
42
|
-
"records.queryRecord",
|
|
43
36
|
"FCP",
|
|
44
37
|
"First Contentful Paint",
|
|
45
38
|
"LCP",
|
|
@@ -47,20 +40,16 @@
|
|
|
47
40
|
"FID",
|
|
48
41
|
"First Input Delay",
|
|
49
42
|
"CLS",
|
|
50
|
-
"Cumulative Layout Shift"
|
|
43
|
+
"Cumulative Layout Shift",
|
|
44
|
+
"INP",
|
|
45
|
+
"Interation to Next Paint",
|
|
46
|
+
"TTFB",
|
|
47
|
+
"Time to First Byte"
|
|
51
48
|
],
|
|
52
49
|
"size-limit": [
|
|
53
50
|
{
|
|
54
|
-
"limit": "
|
|
51
|
+
"limit": "510B",
|
|
55
52
|
"path": "./src/index.js"
|
|
56
53
|
}
|
|
57
|
-
]
|
|
58
|
-
"ava": {
|
|
59
|
-
"require": [
|
|
60
|
-
"esm"
|
|
61
|
-
],
|
|
62
|
-
"files": [
|
|
63
|
-
"test/*.js"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
54
|
+
]
|
|
66
55
|
}
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,8 @@ const maxRetryTimeout = 60 * 1000 // 60s
|
|
|
7
7
|
* @typedef {'ALL_FORM_FACTORS' | 'PHONE' | 'DESKTOP' | 'TABLET'} FormFactor
|
|
8
8
|
* @typedef {'4G' | '3G' | '2G' | 'slow-2G' | 'offline'} Connection
|
|
9
9
|
* @typedef {{ histogram: { start: number | string, end: number | string, density: number }[], percentiles: { p75: number | string } }} MetricValue
|
|
10
|
-
* @typedef {
|
|
10
|
+
* @typedef {{ year: number, month: number, day: number }} MetricDate
|
|
11
|
+
* @typedef {{ firstDate: MetricDate, lastDate: MetricDate }} CollectionPeriod
|
|
11
12
|
* @typedef {{ error: { code: number, message: string, status: string } }} ErrorResponse
|
|
12
13
|
* @typedef {{
|
|
13
14
|
* record: {
|
|
@@ -22,34 +23,78 @@ const maxRetryTimeout = 60 * 1000 // 60s
|
|
|
22
23
|
* largest_contentful_paint?: MetricValue,
|
|
23
24
|
* first_input_delay?: MetricValue,
|
|
24
25
|
* cumulative_layout_shift?: MetricValue,
|
|
25
|
-
*
|
|
26
|
+
* interaction_to_next_paint?: MetricValue,
|
|
27
|
+
* experimental_time_to_first_byte?: MetricValue,
|
|
28
|
+
* },
|
|
29
|
+
* collectionPeriod: CollectionPeriod
|
|
26
30
|
* },
|
|
27
31
|
* urlNormalizationDetails?: {
|
|
28
32
|
* originalUrl: string,
|
|
29
33
|
* normalizedUrl: string
|
|
30
34
|
* }
|
|
31
35
|
* }} SuccessResponse
|
|
36
|
+
*
|
|
37
|
+
* @typedef {(?number | string)[]} PercentileValues
|
|
38
|
+
* @typedef {{ start: number, end?: number, densities: (number | 'NaN')[] }} HistorgramTimeserie
|
|
39
|
+
* @typedef {{
|
|
40
|
+
* histogramTimeseries: HistorgramTimeserie[],
|
|
41
|
+
* percentilesTimeseries: { p75s: PercentileValues }
|
|
42
|
+
* }} HistoryValue
|
|
43
|
+
*
|
|
44
|
+
* @typedef {{
|
|
45
|
+
* record: {
|
|
46
|
+
* key: {
|
|
47
|
+
* url?: string,
|
|
48
|
+
* origin?: string,
|
|
49
|
+
* formFactor?: FormFactor
|
|
50
|
+
* },
|
|
51
|
+
* metrics: {
|
|
52
|
+
* first_input_delay?: HistoryValue,
|
|
53
|
+
* first_contentful_paint?: HistoryValue,
|
|
54
|
+
* largest_contentful_paint?: HistoryValue,
|
|
55
|
+
* cumulative_layout_shift?: HistoryValue,
|
|
56
|
+
* interaction_to_next_paint?: HistoryValue,
|
|
57
|
+
* experimental_time_to_first_byte?: HistoryValue,
|
|
58
|
+
* }
|
|
59
|
+
* collectionPeriods: CollectionPeriod[]
|
|
60
|
+
* },
|
|
61
|
+
* urlNormalizationDetails?: {
|
|
62
|
+
* originalUrl: string,
|
|
63
|
+
* normalizedUrl: string
|
|
64
|
+
* },
|
|
65
|
+
* }} HistoryResponse
|
|
32
66
|
*/
|
|
33
67
|
|
|
68
|
+
/** @param {CreateOptions} createOptions @return {function(QueryRecordOptions): Promise<SuccessResponse | null>} */
|
|
69
|
+
export function createQueryRecord(createOptions) {
|
|
70
|
+
return createQueryCruxApi({ ...createOptions, api: 'record' })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @param {CreateOptions} createOptions @return {function(QueryRecordOptions): Promise<HistoryResponse | null>} */
|
|
74
|
+
export function createQueryHistoryRecord(createOptions) {
|
|
75
|
+
return createQueryCruxApi({ ...createOptions, api: 'history' })
|
|
76
|
+
}
|
|
77
|
+
|
|
34
78
|
/**
|
|
35
79
|
* Fetch CrUX API and handles 4xx errors.
|
|
36
80
|
* Inspired by: https://github.com/GoogleChrome/CrUX/blob/master/js/crux-api-util.js
|
|
37
81
|
*
|
|
38
|
-
* @param {CreateOptions} createOptions
|
|
82
|
+
* @param {CreateOptions & { api: 'history' | 'record' }} createOptions
|
|
39
83
|
*/
|
|
40
84
|
|
|
41
|
-
|
|
85
|
+
function createQueryCruxApi(createOptions) {
|
|
42
86
|
const key = createOptions.key
|
|
43
87
|
const fetch = createOptions.fetch || window.fetch
|
|
44
|
-
|
|
88
|
+
const apiMethod = createOptions.api === 'history' ? 'queryHistoryRecord' : 'queryRecord'
|
|
89
|
+
return queryCruxApi
|
|
45
90
|
|
|
46
91
|
/**
|
|
47
92
|
* @param {QueryRecordOptions} queryOptions
|
|
48
|
-
* @return {Promise<
|
|
93
|
+
* @return {Promise<any | null>}
|
|
49
94
|
*/
|
|
50
95
|
|
|
51
|
-
async function
|
|
52
|
-
const apiEndpoint = `https://chromeuxreport.googleapis.com/v1/records
|
|
96
|
+
async function queryCruxApi(queryOptions, retryCounter = 1) {
|
|
97
|
+
const apiEndpoint = `https://chromeuxreport.googleapis.com/v1/records:${apiMethod}?key=${key}`
|
|
53
98
|
const res = await fetch(apiEndpoint, { method: 'POST', body: JSON.stringify(queryOptions) })
|
|
54
99
|
if (res.status >= 500) throw new Error(`Invalid CrUX API status: ${res.status}`)
|
|
55
100
|
|
|
@@ -57,11 +102,11 @@ export function createQueryRecord(createOptions) {
|
|
|
57
102
|
if (json && json.error) {
|
|
58
103
|
const { error } = /** @type {ErrorResponse} */ (json)
|
|
59
104
|
if (error.code === 404) return null
|
|
60
|
-
if (error.code === 429) return retryAfterTimeout(retryCounter, () =>
|
|
105
|
+
if (error.code === 429) return retryAfterTimeout(retryCounter, () => queryCruxApi(queryOptions, retryCounter + 1))
|
|
61
106
|
throw new Error(JSON.stringify(error))
|
|
62
107
|
}
|
|
63
108
|
if (!json || (json && !json.record.key)) throw new Error(`Invalid response: ${JSON.stringify(json)}`)
|
|
64
|
-
return
|
|
109
|
+
return json
|
|
65
110
|
}
|
|
66
111
|
}
|
|
67
112
|
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{ key: string, fetch?: function }} CreateOptions
|
|
3
|
+
* @typedef {{ url?: string, origin?: string, formFactor?: FormFactor, effectiveConnectionType?: Connection }} QueryRecordOptions
|
|
4
|
+
* @typedef {'ALL_FORM_FACTORS' | 'PHONE' | 'DESKTOP' | 'TABLET'} FormFactor
|
|
5
|
+
* @typedef {'4G' | '3G' | '2G' | 'slow-2G' | 'offline'} Connection
|
|
6
|
+
* @typedef {{ histogram: { start: number | string, end: number | string, density: number }[], percentiles: { p75: number | string } }} MetricValue
|
|
7
|
+
* @typedef {{ year: number, month: number, day: number }} MetricDate
|
|
8
|
+
* @typedef {{ firstDate: MetricDate, lastDate: MetricDate }} CollectionPeriod
|
|
9
|
+
* @typedef {{ error: { code: number, message: string, status: string } }} ErrorResponse
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* record: {
|
|
12
|
+
* key: {
|
|
13
|
+
* url?: string,
|
|
14
|
+
* origin?: string,
|
|
15
|
+
* effectiveConnectionType?: Connection,
|
|
16
|
+
* formFactor?: FormFactor
|
|
17
|
+
* },
|
|
18
|
+
* metrics: {
|
|
19
|
+
* first_contentful_paint?: MetricValue,
|
|
20
|
+
* largest_contentful_paint?: MetricValue,
|
|
21
|
+
* first_input_delay?: MetricValue,
|
|
22
|
+
* cumulative_layout_shift?: MetricValue,
|
|
23
|
+
* interaction_to_next_paint?: MetricValue,
|
|
24
|
+
* experimental_time_to_first_byte?: MetricValue,
|
|
25
|
+
* },
|
|
26
|
+
* collectionPeriod: CollectionPeriod
|
|
27
|
+
* },
|
|
28
|
+
* urlNormalizationDetails?: {
|
|
29
|
+
* originalUrl: string,
|
|
30
|
+
* normalizedUrl: string
|
|
31
|
+
* }
|
|
32
|
+
* }} SuccessResponse
|
|
33
|
+
*
|
|
34
|
+
* @typedef {(?number | string)[]} PercentileValues
|
|
35
|
+
* @typedef {{ start: number, end?: number, densities: (number | 'NaN')[] }} HistorgramTimeserie
|
|
36
|
+
* @typedef {{
|
|
37
|
+
* histogramTimeseries: HistorgramTimeserie[],
|
|
38
|
+
* percentilesTimeseries: { p75s: PercentileValues }
|
|
39
|
+
* }} HistoryValue
|
|
40
|
+
*
|
|
41
|
+
* @typedef {{
|
|
42
|
+
* record: {
|
|
43
|
+
* key: {
|
|
44
|
+
* url?: string,
|
|
45
|
+
* origin?: string,
|
|
46
|
+
* formFactor?: FormFactor
|
|
47
|
+
* },
|
|
48
|
+
* metrics: {
|
|
49
|
+
* first_input_delay?: HistoryValue,
|
|
50
|
+
* first_contentful_paint?: HistoryValue,
|
|
51
|
+
* largest_contentful_paint?: HistoryValue,
|
|
52
|
+
* cumulative_layout_shift?: HistoryValue,
|
|
53
|
+
* interaction_to_next_paint?: HistoryValue,
|
|
54
|
+
* experimental_time_to_first_byte?: HistoryValue,
|
|
55
|
+
* }
|
|
56
|
+
* collectionPeriods: CollectionPeriod[]
|
|
57
|
+
* },
|
|
58
|
+
* urlNormalizationDetails?: {
|
|
59
|
+
* originalUrl: string,
|
|
60
|
+
* normalizedUrl: string
|
|
61
|
+
* },
|
|
62
|
+
* }} HistoryResponse
|
|
63
|
+
*/
|
|
64
|
+
/** @param {CreateOptions} createOptions @return {function(QueryRecordOptions): Promise<SuccessResponse | null>} */
|
|
65
|
+
export function createQueryRecord(createOptions: CreateOptions): (arg0: QueryRecordOptions) => Promise<SuccessResponse | null>;
|
|
66
|
+
/** @param {CreateOptions} createOptions @return {function(QueryRecordOptions): Promise<HistoryResponse | null>} */
|
|
67
|
+
export function createQueryHistoryRecord(createOptions: CreateOptions): (arg0: QueryRecordOptions) => Promise<HistoryResponse | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Normalize URL to match CrUX API key.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} url
|
|
72
|
+
*/
|
|
73
|
+
export function normalizeUrl(url: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Random delay from 1ms to `maxRetryTimeout`.
|
|
76
|
+
* Random logic is based on: https://stackoverflow.com/a/29246176
|
|
77
|
+
*
|
|
78
|
+
* @param {number} retryCounter
|
|
79
|
+
* @param {function} request
|
|
80
|
+
*/
|
|
81
|
+
export function retryAfterTimeout(retryCounter: number, request: Function): Promise<any>;
|
|
82
|
+
export type CreateOptions = {
|
|
83
|
+
key: string;
|
|
84
|
+
fetch?: Function;
|
|
85
|
+
};
|
|
86
|
+
export type QueryRecordOptions = {
|
|
87
|
+
url?: string;
|
|
88
|
+
origin?: string;
|
|
89
|
+
formFactor?: FormFactor;
|
|
90
|
+
effectiveConnectionType?: Connection;
|
|
91
|
+
};
|
|
92
|
+
export type FormFactor = 'ALL_FORM_FACTORS' | 'PHONE' | 'DESKTOP' | 'TABLET';
|
|
93
|
+
export type Connection = '4G' | '3G' | '2G' | 'slow-2G' | 'offline';
|
|
94
|
+
export type MetricValue = {
|
|
95
|
+
histogram: {
|
|
96
|
+
start: number | string;
|
|
97
|
+
end: number | string;
|
|
98
|
+
density: number;
|
|
99
|
+
}[];
|
|
100
|
+
percentiles: {
|
|
101
|
+
p75: number | string;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
export type MetricDate = {
|
|
105
|
+
year: number;
|
|
106
|
+
month: number;
|
|
107
|
+
day: number;
|
|
108
|
+
};
|
|
109
|
+
export type CollectionPeriod = {
|
|
110
|
+
firstDate: MetricDate;
|
|
111
|
+
lastDate: MetricDate;
|
|
112
|
+
};
|
|
113
|
+
export type ErrorResponse = {
|
|
114
|
+
error: {
|
|
115
|
+
code: number;
|
|
116
|
+
message: string;
|
|
117
|
+
status: string;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
export type SuccessResponse = {
|
|
121
|
+
record: {
|
|
122
|
+
key: {
|
|
123
|
+
url?: string;
|
|
124
|
+
origin?: string;
|
|
125
|
+
effectiveConnectionType?: Connection;
|
|
126
|
+
formFactor?: FormFactor;
|
|
127
|
+
};
|
|
128
|
+
metrics: {
|
|
129
|
+
first_contentful_paint?: MetricValue;
|
|
130
|
+
largest_contentful_paint?: MetricValue;
|
|
131
|
+
first_input_delay?: MetricValue;
|
|
132
|
+
cumulative_layout_shift?: MetricValue;
|
|
133
|
+
interaction_to_next_paint?: MetricValue;
|
|
134
|
+
experimental_time_to_first_byte?: MetricValue;
|
|
135
|
+
};
|
|
136
|
+
collectionPeriod: CollectionPeriod;
|
|
137
|
+
};
|
|
138
|
+
urlNormalizationDetails?: {
|
|
139
|
+
originalUrl: string;
|
|
140
|
+
normalizedUrl: string;
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
export type PercentileValues = ((number | string) | null)[];
|
|
144
|
+
export type HistorgramTimeserie = {
|
|
145
|
+
start: number;
|
|
146
|
+
end?: number;
|
|
147
|
+
densities: (number | 'NaN')[];
|
|
148
|
+
};
|
|
149
|
+
export type HistoryValue = {
|
|
150
|
+
histogramTimeseries: HistorgramTimeserie[];
|
|
151
|
+
percentilesTimeseries: {
|
|
152
|
+
p75s: (string | number)[];
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
export type HistoryResponse = {
|
|
156
|
+
record: {
|
|
157
|
+
key: {
|
|
158
|
+
url?: string;
|
|
159
|
+
origin?: string;
|
|
160
|
+
formFactor?: FormFactor;
|
|
161
|
+
};
|
|
162
|
+
metrics: {
|
|
163
|
+
first_input_delay?: HistoryValue;
|
|
164
|
+
first_contentful_paint?: HistoryValue;
|
|
165
|
+
largest_contentful_paint?: HistoryValue;
|
|
166
|
+
cumulative_layout_shift?: HistoryValue;
|
|
167
|
+
interaction_to_next_paint?: HistoryValue;
|
|
168
|
+
experimental_time_to_first_byte?: HistoryValue;
|
|
169
|
+
};
|
|
170
|
+
collectionPeriods: CollectionPeriod[];
|
|
171
|
+
};
|
|
172
|
+
urlNormalizationDetails?: {
|
|
173
|
+
originalUrl: string;
|
|
174
|
+
normalizedUrl: string;
|
|
175
|
+
};
|
|
176
|
+
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create batch interface for CrUX API.
|
|
3
|
-
* https://developers.google.com/web/tools/chrome-user-experience-report/api/guides/batch
|
|
4
|
-
*
|
|
5
|
-
* @typedef {{ options: import('../..').QueryRecordOptions, result: import('../..').SuccessResponse | null | undefined }[]} BatchValues
|
|
6
|
-
*
|
|
7
|
-
* @param {import('../..').CreateOptions} createOptions
|
|
8
|
-
*/
|
|
9
|
-
export function createBatch(createOptions: import('../..').CreateOptions): (batchOptions: import('../..').BatchOptions) => Promise<import("../..").BatchResponse>;
|
|
10
|
-
/**
|
|
11
|
-
* Create batch interface for CrUX API.
|
|
12
|
-
* https://developers.google.com/web/tools/chrome-user-experience-report/api/guides/batch
|
|
13
|
-
*/
|
|
14
|
-
export type BatchValues = {
|
|
15
|
-
options: import('../..').QueryRecordOptions;
|
|
16
|
-
result: import('../..').SuccessResponse | null | undefined;
|
|
17
|
-
}[];
|
package/dist/crux-api.d.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {{ key: string, fetch?: function }} CreateOptions
|
|
3
|
-
* @typedef {{ url?: string, origin?: string, formFactor?: FormFactor, effectiveConnectionType?: Connection }} QueryRecordOptions
|
|
4
|
-
* @typedef {'ALL_FORM_FACTORS' | 'PHONE' | 'DESKTOP' | 'TABLET'} FormFactor
|
|
5
|
-
* @typedef {'4G' | '3G' | '2G' | 'slow-2G' | 'offline'} Connection
|
|
6
|
-
* @typedef {{ histogram: { start: number | string, end: number | string, density: number }[], percentiles: { p75: number | string } }} MetricValue
|
|
7
|
-
* @typedef {'first_contentful_paint' | 'largest_contentful_paint' | 'first_input_delay' | 'cumulative_layout_shift'} MetricName
|
|
8
|
-
* @typedef {{ error: { code: number, message: string, status: string } }} ErrorResponse
|
|
9
|
-
* @typedef {{
|
|
10
|
-
* record: {
|
|
11
|
-
* key: {
|
|
12
|
-
* url?: string,
|
|
13
|
-
* origin?: string,
|
|
14
|
-
* effectiveConnectionType?: Connection,
|
|
15
|
-
* formFactor?: FormFactor
|
|
16
|
-
* },
|
|
17
|
-
* metrics: {
|
|
18
|
-
* first_contentful_paint?: MetricValue,
|
|
19
|
-
* largest_contentful_paint?: MetricValue,
|
|
20
|
-
* first_input_delay?: MetricValue,
|
|
21
|
-
* cumulative_layout_shift?: MetricValue,
|
|
22
|
-
* }
|
|
23
|
-
* },
|
|
24
|
-
* urlNormalizationDetails?: {
|
|
25
|
-
* originalUrl: string,
|
|
26
|
-
* normalizedUrl: string
|
|
27
|
-
* }
|
|
28
|
-
* }} SuccessResponse
|
|
29
|
-
*/
|
|
30
|
-
/**
|
|
31
|
-
* Fetch CrUX API and handles 4xx errors.
|
|
32
|
-
* Inspired by: https://github.com/GoogleChrome/CrUX/blob/master/js/crux-api-util.js
|
|
33
|
-
*
|
|
34
|
-
* @param {CreateOptions} createOptions
|
|
35
|
-
*/
|
|
36
|
-
export function createQueryRecord(createOptions: CreateOptions): (queryOptions: QueryRecordOptions, retryCounter?: number) => Promise<SuccessResponse | null>;
|
|
37
|
-
/**
|
|
38
|
-
* Normalize URL to match CrUX API key.
|
|
39
|
-
*
|
|
40
|
-
* @param {string} url
|
|
41
|
-
*/
|
|
42
|
-
export function normalizeUrl(url: string): string;
|
|
43
|
-
/**
|
|
44
|
-
* Random delay from 1ms to `maxRetryTimeout`.
|
|
45
|
-
* Random logic is based on: https://stackoverflow.com/a/29246176
|
|
46
|
-
*
|
|
47
|
-
* @param {number} retryCounter
|
|
48
|
-
* @param {function} request
|
|
49
|
-
*/
|
|
50
|
-
export function retryAfterTimeout(retryCounter: number, request: Function): Promise<any>;
|
|
51
|
-
export type CreateOptions = {
|
|
52
|
-
key: string;
|
|
53
|
-
fetch?: Function;
|
|
54
|
-
};
|
|
55
|
-
export type QueryRecordOptions = {
|
|
56
|
-
url?: string;
|
|
57
|
-
origin?: string;
|
|
58
|
-
formFactor?: FormFactor;
|
|
59
|
-
effectiveConnectionType?: Connection;
|
|
60
|
-
};
|
|
61
|
-
export type FormFactor = 'ALL_FORM_FACTORS' | 'PHONE' | 'DESKTOP' | 'TABLET';
|
|
62
|
-
export type Connection = '4G' | '3G' | '2G' | 'slow-2G' | 'offline';
|
|
63
|
-
export type MetricValue = {
|
|
64
|
-
histogram: {
|
|
65
|
-
start: number | string;
|
|
66
|
-
end: number | string;
|
|
67
|
-
density: number;
|
|
68
|
-
}[];
|
|
69
|
-
percentiles: {
|
|
70
|
-
p75: number | string;
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
export type MetricName = 'first_contentful_paint' | 'largest_contentful_paint' | 'first_input_delay' | 'cumulative_layout_shift';
|
|
74
|
-
export type ErrorResponse = {
|
|
75
|
-
error: {
|
|
76
|
-
code: number;
|
|
77
|
-
message: string;
|
|
78
|
-
status: string;
|
|
79
|
-
};
|
|
80
|
-
};
|
|
81
|
-
export type SuccessResponse = {
|
|
82
|
-
record: {
|
|
83
|
-
key: {
|
|
84
|
-
url?: string;
|
|
85
|
-
origin?: string;
|
|
86
|
-
effectiveConnectionType?: Connection;
|
|
87
|
-
formFactor?: FormFactor;
|
|
88
|
-
};
|
|
89
|
-
metrics: {
|
|
90
|
-
first_contentful_paint?: MetricValue;
|
|
91
|
-
largest_contentful_paint?: MetricValue;
|
|
92
|
-
first_input_delay?: MetricValue;
|
|
93
|
-
cumulative_layout_shift?: MetricValue;
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
urlNormalizationDetails?: {
|
|
97
|
-
originalUrl: string;
|
|
98
|
-
normalizedUrl: string;
|
|
99
|
-
};
|
|
100
|
-
};
|
package/dist/crux-api.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var r=function(r,o){try{if(r<=e){var n=Math.floor(Math.random()*t)+1;return Promise.resolve(new Promise(function(r){return setTimeout(r,n)})).then(function(){return o()})}throw new Error("Max retries reached")}catch(r){return Promise.reject(r)}},e=10,t=6e4;exports.createQueryRecord=function(e){var t=e.key,o=e.fetch||window.fetch;return function e(n,i){void 0===i&&(i=1);try{return Promise.resolve(o("https://chromeuxreport.googleapis.com/v1/records:queryRecord?key="+t,{method:"POST",body:JSON.stringify(n)})).then(function(t){if(t.status>=500)throw new Error("Invalid CrUX API status: "+t.status);return Promise.resolve(t.json()).then(function(t){if(t&&t.error){var o=t.error;if(404===o.code)return null;if(429===o.code)return r(i,function(){return e(n,i+1)});throw new Error(JSON.stringify(o))}if(!t||t&&!t.record.key)throw new Error("Invalid response: "+JSON.stringify(t));return t})})}catch(r){return Promise.reject(r)}}},exports.normalizeUrl=function(r){var e=new URL(r);return e.origin+e.pathname},exports.retryAfterTimeout=r;
|
package/dist/keys.d.ts
DELETED
package/dist/keys.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Create keys rotating function.
|
|
4
|
-
*
|
|
5
|
-
* @param {string[]} [keys]
|
|
6
|
-
* @param {string} [key]
|
|
7
|
-
*/
|
|
8
|
-
exports.__esModule = true;
|
|
9
|
-
exports.createGetKey = void 0;
|
|
10
|
-
function createGetKey(keys, key) {
|
|
11
|
-
var allKeys = /** @type {string[]} */ (keys || [key]).filter(Boolean);
|
|
12
|
-
var totalKeys = allKeys.length;
|
|
13
|
-
if (totalKeys === 0)
|
|
14
|
-
throw new Error('No API keys');
|
|
15
|
-
var keysStart = Math.floor(Math.random() * totalKeys); // random between 0 and totalKeys https://stackoverflow.com/a/7228322/893744
|
|
16
|
-
return function getKey() {
|
|
17
|
-
keysStart = keysStart < totalKeys - 1 ? keysStart + 1 : 0;
|
|
18
|
-
return allKeys[keysStart];
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
exports.createGetKey = createGetKey;
|
package/dist/retry.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Random delay from 1ms to `maxRetryTimeout`.
|
|
3
|
-
* Random logic is based on: https://stackoverflow.com/a/29246176
|
|
4
|
-
*
|
|
5
|
-
* @param {number} retryCounter
|
|
6
|
-
* @param {function} request
|
|
7
|
-
*/
|
|
8
|
-
export function retryAfterTimeout(retryCounter: number, request: Function): Promise<any>;
|
package/dist/retry.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
-
function step(op) {
|
|
16
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
-
while (_) try {
|
|
18
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
-
switch (op[0]) {
|
|
21
|
-
case 0: case 1: t = op; break;
|
|
22
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
-
default:
|
|
26
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
-
if (t[2]) _.ops.pop();
|
|
31
|
-
_.trys.pop(); continue;
|
|
32
|
-
}
|
|
33
|
-
op = body.call(thisArg, _);
|
|
34
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
exports.__esModule = true;
|
|
39
|
-
exports.retryAfterTimeout = void 0;
|
|
40
|
-
var maxRetries = 10;
|
|
41
|
-
var maxRetryTimeout = 100 * 1000; // 100s
|
|
42
|
-
/**
|
|
43
|
-
* Random delay from 1ms to `maxRetryTimeout`.
|
|
44
|
-
* Random logic is based on: https://stackoverflow.com/a/29246176
|
|
45
|
-
*
|
|
46
|
-
* @param {number} retryCounter
|
|
47
|
-
* @param {function} request
|
|
48
|
-
*/
|
|
49
|
-
function retryAfterTimeout(retryCounter, request) {
|
|
50
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
51
|
-
var timeout_1;
|
|
52
|
-
return __generator(this, function (_a) {
|
|
53
|
-
switch (_a.label) {
|
|
54
|
-
case 0:
|
|
55
|
-
if (!(retryCounter <= maxRetries)) return [3 /*break*/, 2];
|
|
56
|
-
timeout_1 = Math.floor(Math.random() * maxRetryTimeout) + 1;
|
|
57
|
-
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, timeout_1); })];
|
|
58
|
-
case 1:
|
|
59
|
-
_a.sent();
|
|
60
|
-
return [2 /*return*/, request()];
|
|
61
|
-
case 2: throw new Error('Max retries reached');
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
exports.retryAfterTimeout = retryAfterTimeout;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/script/batch.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|