@automattic/jetpack-boost-score-api 0.1.7 → 0.1.8
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/CHANGELOG.md +5 -0
- package/build/api-error.d.ts +14 -0
- package/build/api.d.ts +25 -0
- package/build/config.d.ts +2 -0
- package/build/index.asset.php +1 -0
- package/build/index.d.ts +88 -0
- package/build/index.js +7 -0
- package/{src/utils/cast-to-number.ts → build/utils/cast-to-number.d.ts} +1 -17
- package/{src/utils/cast-to-string.ts → build/utils/cast-to-string.d.ts} +1 -18
- package/{src/utils/json-object-type.ts → build/utils/json-object-type.d.ts} +6 -4
- package/{src/utils/json-types.ts → build/utils/json-types.d.ts} +3 -10
- package/build/utils/poll-promise.d.ts +24 -0
- package/build/utils/standardize-error.d.ts +10 -0
- package/package.json +1 -1
- package/.gitattributes +0 -16
- package/babel.config.cjs +0 -10
- package/src/api-error.ts +0 -65
- package/src/api.ts +0 -156
- package/src/config.ts +0 -2
- package/src/index.ts +0 -283
- package/src/utils/poll-promise.ts +0 -52
- package/src/utils/standardize-error.ts +0 -33
- package/webpack.config.cjs +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.8] - 2023-09-01
|
|
9
|
+
### Removed
|
|
10
|
+
- Remove unnecessary files from mirror repo and published package. [#32674]
|
|
11
|
+
|
|
8
12
|
## [0.1.7] - 2023-08-28
|
|
9
13
|
### Added
|
|
10
14
|
- New section to display boost history on jetpack boost admin page [#32016]
|
|
@@ -46,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
46
50
|
### Added
|
|
47
51
|
- Create package for the boost score bar API [#30781]
|
|
48
52
|
|
|
53
|
+
[0.1.8]: https://github.com/Automattic/jetpack-boost-score-api/compare/v0.1.7...v0.1.8
|
|
49
54
|
[0.1.7]: https://github.com/Automattic/jetpack-boost-score-api/compare/v0.1.6...v0.1.7
|
|
50
55
|
[0.1.6]: https://github.com/Automattic/jetpack-boost-score-api/compare/v0.1.5...v0.1.6
|
|
51
56
|
[0.1.5]: https://github.com/Automattic/jetpack-boost-score-api/compare/v0.1.4...v0.1.5
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JSONObject } from './utils/json-object-type';
|
|
2
|
+
/**
|
|
3
|
+
* Special error subclass returned by API Calls with extra
|
|
4
|
+
* information.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApiError extends Error {
|
|
7
|
+
readonly httpCode: number;
|
|
8
|
+
readonly body: JSONObject | string | null;
|
|
9
|
+
readonly parseError: Error | null;
|
|
10
|
+
constructor(httpCode: number, body: JSONObject | string | null, parseError: Error | null);
|
|
11
|
+
get message(): string;
|
|
12
|
+
getDisplayBody(): string;
|
|
13
|
+
getRestApiErrorMessage(): string;
|
|
14
|
+
}
|
package/build/api.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { JSONObject } from './utils/json-object-type';
|
|
2
|
+
/**
|
|
3
|
+
* Make a GET request to the Boost REST API.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} root - The root URL to use.
|
|
6
|
+
* @param {string} path - The path to the endpoint.
|
|
7
|
+
* @param {string} nonce - The nonce to use.
|
|
8
|
+
* @returns {Promise} - The response.
|
|
9
|
+
*/
|
|
10
|
+
declare function get<T = JSONObject>(root: string, path: string, nonce: string): Promise<T>;
|
|
11
|
+
/**
|
|
12
|
+
* Make a POST request to the Boost REST API.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} root - The root URL to use.
|
|
15
|
+
* @param {string} path - The path to the endpoint.
|
|
16
|
+
* @param {null | JSONObject} body - The body of the request.
|
|
17
|
+
* @param {string} nonce - The nonce to use.
|
|
18
|
+
* @returns {Promise} - The response.
|
|
19
|
+
*/
|
|
20
|
+
declare function post<T = JSONObject>(root: string, path: string, body: JSONObject | null, nonce: string): Promise<T>;
|
|
21
|
+
declare const _default: {
|
|
22
|
+
get: typeof get;
|
|
23
|
+
post: typeof post;
|
|
24
|
+
};
|
|
25
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?php return array('dependencies' => array('wp-i18n'), 'version' => '58dd44b48c4070487c17');
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
type SpeedScores = {
|
|
2
|
+
mobile: number;
|
|
3
|
+
desktop: number;
|
|
4
|
+
};
|
|
5
|
+
type SpeedScoresSet = {
|
|
6
|
+
current: SpeedScores;
|
|
7
|
+
noBoost: SpeedScores;
|
|
8
|
+
isStale: boolean;
|
|
9
|
+
};
|
|
10
|
+
interface ScoreValue {
|
|
11
|
+
score: number;
|
|
12
|
+
value: number;
|
|
13
|
+
}
|
|
14
|
+
interface SpeedHistoryDimension {
|
|
15
|
+
mobile_overall_score: number;
|
|
16
|
+
desktop_overall_score: number;
|
|
17
|
+
mobile_lcp: ScoreValue;
|
|
18
|
+
desktop_lcp: ScoreValue;
|
|
19
|
+
mobile_tbt: ScoreValue;
|
|
20
|
+
desktop_tbt: ScoreValue;
|
|
21
|
+
mobile_cls: ScoreValue;
|
|
22
|
+
desktop_cls: ScoreValue;
|
|
23
|
+
mobile_si: ScoreValue;
|
|
24
|
+
desktop_si: ScoreValue;
|
|
25
|
+
mobile_tti: ScoreValue;
|
|
26
|
+
desktop_tti: ScoreValue;
|
|
27
|
+
mobile_fcp: ScoreValue;
|
|
28
|
+
desktop_fcp: ScoreValue;
|
|
29
|
+
}
|
|
30
|
+
interface SpeedHistoryPeriod {
|
|
31
|
+
timestamp: number;
|
|
32
|
+
dimensions: SpeedHistoryDimension[];
|
|
33
|
+
}
|
|
34
|
+
interface SpeedHistoryResponse {
|
|
35
|
+
data: {
|
|
36
|
+
_meta: {
|
|
37
|
+
start: number;
|
|
38
|
+
end: number;
|
|
39
|
+
};
|
|
40
|
+
periods: SpeedHistoryPeriod[];
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Kick off a request to generate speed scores for this site. Will automatically
|
|
45
|
+
* poll for a response until the task is done, returning a SpeedScores object.
|
|
46
|
+
*
|
|
47
|
+
* @param {boolean} force - Force regenerate speed scores.
|
|
48
|
+
* @param {string} rootUrl - Root URL for the HTTP request.
|
|
49
|
+
* @param {string} siteUrl - URL of the site.
|
|
50
|
+
* @param {string} nonce - Nonce to use for authentication.
|
|
51
|
+
* @returns {SpeedScoresSet} Speed scores returned by the server.
|
|
52
|
+
*/
|
|
53
|
+
export declare function requestSpeedScores(force: boolean, rootUrl: string, siteUrl: string, nonce: string): Promise<SpeedScoresSet>;
|
|
54
|
+
/**
|
|
55
|
+
* Get SpeedScores gistory to render the Graph. Will automatically
|
|
56
|
+
* poll for a response until the task is done, returning a SpeedHistory object.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} rootUrl - Root URL for the HTTP request.
|
|
59
|
+
* @param {string} siteUrl - URL of the site.
|
|
60
|
+
* @param {string} nonce - Nonce to use for authentication.
|
|
61
|
+
* @returns {SpeedHistoryResponse} Speed score history returned by the server.
|
|
62
|
+
*/
|
|
63
|
+
export declare function requestSpeedScoresHistory(rootUrl: string, siteUrl: string, nonce: string): Promise<SpeedHistoryResponse>;
|
|
64
|
+
/**
|
|
65
|
+
* Given a mobile and desktop score, return a letter summarizing the overall
|
|
66
|
+
* score.
|
|
67
|
+
*
|
|
68
|
+
* @param {number} mobile - Mobile speed score
|
|
69
|
+
* @param {number} desktop - Desktop speed score
|
|
70
|
+
* @returns {string} letter score
|
|
71
|
+
*/
|
|
72
|
+
export declare function getScoreLetter(mobile: number, desktop: number): string;
|
|
73
|
+
/**
|
|
74
|
+
* Find out if site scores changed. We fire a popout modal if they improve or worsen.
|
|
75
|
+
* The message varies depending on the results of the speed scores so lets modify this
|
|
76
|
+
*
|
|
77
|
+
* @param {SpeedScoresSet} scores - Speed scores returned by the server.
|
|
78
|
+
* @returns {boolean} true if scores changed.
|
|
79
|
+
*/
|
|
80
|
+
export declare function didScoresChange(scores: SpeedScoresSet): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Determine the change in scores to pass through to other functions.
|
|
83
|
+
*
|
|
84
|
+
* @param {SpeedScoresSet} scores - Speed scores returned by the server.
|
|
85
|
+
* @returns {number} - The change in scores in percentage.
|
|
86
|
+
*/
|
|
87
|
+
export declare function getScoreMovementPercentage(scores: SpeedScoresSet): number;
|
|
88
|
+
export {};
|
package/build/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.BoostScoreApiLibrary=t():e.BoostScoreApiLibrary=t()}(self,(()=>(()=>{"use strict";var e={477:(e,t,r)=>{r.d(t,{M:()=>c});var n,o=r(736),i=r(752),s=r(283),u=(n=function(e,t){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},n(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function __(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(__.prototype=t.prototype,new __)}),c=function(e){function t(t,r,n){var o=e.call(this)||this;return o.httpCode=t,o.body=r,o.parseError=n,o}return u(t,e),Object.defineProperty(t.prototype,"message",{get:function(){switch(this.httpCode){case 403:return this.getRestApiErrorMessage();case 200:if(this.parseError)return(0,o.sprintf)(
|
|
2
|
+
/* Translators: %s refers to a browser-supplied error message (hopefully already in the right language) */
|
|
3
|
+
(0,o.__)("Received invalid response while communicating with your WordPress site: %s","boost-score-api"),this.parseError.message)}return(0,o.sprintf)(
|
|
4
|
+
/* Translators: %d refers to numeric HTTP error code */
|
|
5
|
+
(0,o.__)("HTTP %d error received while communicating with the server.","boost-score-api"),this.httpCode)},enumerable:!1,configurable:!0}),t.prototype.getDisplayBody=function(){return(0,s.b)(this.body)?JSON.stringify(this.body,null," "):(0,i.x)(this.body,"").substring(0,1e3)},t.prototype.getRestApiErrorMessage=function(){return(0,o.__)("Your site's REST API does not seem to be accessible. Jetpack Boost requires access to your REST API in order to receive site performance scores. Please make sure that your site's REST API is active and accessible, and try again.","boost-score-api")},t}(Error)},655:(e,t,r)=>{r.d(t,{Z:()=>l});var n=r(736),o=r(477),i=r(73),s=function(e,t,r,n){return new(r||(r=Promise))((function(o,i){function s(e){try{c(n.next(e))}catch(e){i(e)}}function u(e){try{c(n.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,u)}c((n=n.apply(e,t||[])).next())}))},u=function(e,t){var r,n,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(s=0)),s;)try{if(r=1,n&&(o=2&u[0]?n.return:u[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,u[1])).done)return o;switch(n=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return s.label++,{value:u[1],done:!1};case 5:s.label++,n=u[1],u=[0];continue;case 7:u=s.ops.pop(),s.trys.pop();continue;default:if(!(o=s.trys,(o=o.length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){s=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){s.label=u[1];break}if(6===u[0]&&s.label<o[1]){s.label=o[1],o=u;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(u);break}o[2]&&s.ops.pop(),s.trys.pop();continue}u=t.call(e,s)}catch(e){u=[6,e],n=0}finally{r=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}};function c(e,t,r,o,c){return void 0===o&&(o=null),s(this,void 0,void 0,(function(){var s,a,l,f,p,d;return u(this,(function(u){switch(u.label){case 0:s={method:e,mode:"cors",headers:{"X-WP-Nonce":c}},"post"!==e&&"delete"!==e||!o||(s.body=JSON.stringify(o),s.headers["Content-Type"]="application/json"),a=function(e,t){return t+i.N+i.L+e}(r,t),u.label=1;case 1:return u.trys.push([1,3,,4]),[4,fetch(a,s)];case 2:return l=u.sent(),[3,4];case 3:throw f=u.sent(),delete(p=s).body,delete p.headers["X-WP-Nonce"],d={requestInitiator:window.location.href,requestUrl:a,requestArgs:p,originalErrorMessage:f.toString()},new Error((0,n.sprintf)(
|
|
6
|
+
/* Translators: %s refers to a string representation of an error object containing useful debug information */
|
|
7
|
+
(0,n.__)("An error occurred while trying to communicate with the site REST API. Extra debug info: %s","boost-score-api"),JSON.stringify(d)));case 4:return[2,l]}}))}))}function a(e,t,r,n,i){return void 0===n&&(n=null),s(this,void 0,void 0,(function(){var s,a,l,f;return u(this,(function(u){switch(u.label){case 0:return[4,c(e,t,r,n,i)];case 1:s=u.sent(),u.label=2;case 2:return u.trys.push([2,4,,5]),[4,s.text()];case 3:return a=u.sent(),[3,5];case 4:throw l=u.sent(),new o.M(s.status,null,l);case 5:try{f=JSON.parse(a)}catch(e){throw new o.M(s.status,a,e)}if(!s.ok)throw new o.M(s.status,f,null);return[2,f]}}))}))}const l={get:function(e,t,r){return a("get",e,t,null,r)},post:function(e,t,r,n){return void 0===r&&(r=null),a("post",e,t,r,n)}}},73:(e,t,r)=>{r.d(t,{L:()=>o,N:()=>n});var n="jetpack-boost/v1",o=""},194:(e,t,r)=>{function n(e,t){if(void 0===t&&(t=void 0),"number"==typeof e)return e;if("string"==typeof e){var r=parseFloat(e);if(!isNaN(r))return r}return t}r.d(t,{W:()=>n})},752:(e,t,r)=>{function n(e,t){return void 0===t&&(t=void 0),"string"==typeof e?e:e&&e.toString instanceof Function?e.toString():t}r.d(t,{x:()=>n})},283:(e,t,r)=>{function n(e){return!!e&&e instanceof Object&&!(e instanceof Array)}r.d(t,{b:()=>n})},281:(e,t,r)=>{r.d(t,{Z:()=>s});var n=r(736),o=function(e,t,r,n){return new(r||(r=Promise))((function(o,i){function s(e){try{c(n.next(e))}catch(e){i(e)}}function u(e){try{c(n.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,u)}c((n=n.apply(e,t||[])).next())}))},i=function(e,t){var r,n,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(s=0)),s;)try{if(r=1,n&&(o=2&u[0]?n.return:u[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,u[1])).done)return o;switch(n=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return s.label++,{value:u[1],done:!1};case 5:s.label++,n=u[1],u=[0];continue;case 7:u=s.ops.pop(),s.trys.pop();continue;default:if(!(o=s.trys,(o=o.length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){s=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){s.label=u[1];break}if(6===u[0]&&s.label<o[1]){s.label=o[1],o=u;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(u);break}o[2]&&s.ops.pop(),s.trys.pop();continue}u=t.call(e,s)}catch(e){u=[6,e],n=0}finally{r=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}};function s(e){var t=e.interval,r=e.callback,s=e.timeout,u=e.timeoutError;return o(this,void 0,void 0,(function(){var e,c,a=this;return i(this,(function(l){return[2,new Promise((function(l,f){e=setTimeout((function(){f(new Error(u||(0,n.__)("Timed out","boost-score-api")))}),s||12e4),c=setInterval((function(){return o(a,void 0,void 0,(function(){var e;return i(this,(function(t){switch(t.label){case 0:return t.trys.push([0,2,,3]),[4,Promise.resolve(r(l))];case 1:return t.sent(),[3,3];case 2:return e=t.sent(),f(e),[3,3];case 3:return[2]}}))}))}),t)})).finally((function(){clearTimeout(e),clearInterval(c)}))]}))}))}},595:(e,t,r)=>{function n(e,t){return e instanceof Error?e:"string"==typeof e||e instanceof String?new Error(e.toString()):e.message?new Error(e.message):t?new Error(t):new Error(JSON.stringify(e))}r.d(t,{V:()=>n})},736:e=>{e.exports=window.wp.i18n}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};return(()=>{r.r(n),r.d(n,{didScoresChange:()=>w,getScoreLetter:()=>v,getScoreMovementPercentage:()=>g,requestSpeedScores:()=>d,requestSpeedScoresHistory:()=>h});var e=r(736),t=r(655),o=r(194),i=r(752),s=r(283),u=r(281),c=r(595),a=function(e,t,r,n){return new(r||(r=Promise))((function(o,i){function s(e){try{c(n.next(e))}catch(e){i(e)}}function u(e){try{c(n.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,u)}c((n=n.apply(e,t||[])).next())}))},l=function(e,t){var r,n,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(s=0)),s;)try{if(r=1,n&&(o=2&u[0]?n.return:u[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,u[1])).done)return o;switch(n=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return s.label++,{value:u[1],done:!1};case 5:s.label++,n=u[1],u=[0];continue;case 7:u=s.ops.pop(),s.trys.pop();continue;default:if(!(o=s.trys,(o=o.length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){s=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){s.label=u[1];break}if(6===u[0]&&s.label<o[1]){s.label=o[1],o=u;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(u);break}o[2]&&s.ops.pop(),s.trys.pop();continue}u=t.call(e,s)}catch(e){u=[6,e],n=0}finally{r=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}},f=12e4,p=5e3;function d(e,r,n,o){return void 0===e&&(e=!1),a(this,void 0,void 0,(function(){var i,s;return l(this,(function(u){switch(u.label){case 0:return s=b,[4,t.Z.post(r,e?"/speed-scores/refresh":"/speed-scores",{url:n},o)];case 1:return(i=s.apply(void 0,[u.sent()])).scores?[2,i.scores]:[4,y(r,n,o)];case 2:return[2,u.sent()]}}))}))}function h(e,r,n){return a(this,void 0,void 0,(function(){var r,o;return l(this,(function(i){switch(i.label){case 0:return r=(new Date).getTime(),o=r-2592e6,[4,t.Z.post(e,"/speed-scores-history",{start:o,end:r},n)];case 1:return[2,i.sent()]}}))}))}function b(t){if(t.error){var r=(0,e.__)("An unknown error occurred while requesting metrics","boost-score-api");throw(0,c.V)(t.error,r)}if((0,s.b)(t.scores))return{status:"success",scores:{current:(0,s.b)(t.scores.current)?{mobile:(0,o.W)(t.scores.current.mobile,0),desktop:(0,o.W)(t.scores.current.desktop,0)}:{mobile:0,desktop:0},noBoost:(0,s.b)(t.scores.noBoost)?{mobile:(0,o.W)(t.scores.noBoost.mobile,0),desktop:(0,o.W)(t.scores.noBoost.desktop,0)}:null,isStale:!!t.scores.isStale}};var n=(0,i.x)(t.status);if(!n)throw new Error((0,e.__)("Invalid response while requesting metrics","boost-score-api"));return{status:n}}function y(r,n,o){return a(this,void 0,void 0,(function(){var i=this;return l(this,(function(s){return[2,(0,u.Z)({timeout:f,interval:p,timeoutError:(0,e.__)("Timed out while waiting for speed-score.","boost-score-api"),callback:function(e){return a(i,void 0,void 0,(function(){var i,s;return l(this,(function(u){switch(u.label){case 0:return s=b,[4,t.Z.post(r,"/speed-scores",{url:n},o)];case 1:return(i=s.apply(void 0,[u.sent()])).scores&&e(i.scores),[2]}}))}))}})]}))}))}function v(e,t){var r=(e+t)/2;return r>90?"A":r>75?"B":r>50?"C":r>35?"D":r>25?"E":"F"}function w(e){var t=e.current,r=e.noBoost;return null!=t&&null!=r&&(t.mobile!==r.mobile||t.desktop!==r.desktop)}function g(e){var t=e.current,r=e.noBoost;if(null!==t&&null!==r){var n=(e.current.mobile+e.current.desktop)/(e.noBoost.mobile+e.noBoost.desktop)-1;return Math.round(100*n)}return 0}})(),n})()));
|
|
@@ -8,20 +8,4 @@
|
|
|
8
8
|
* @param {DefaultType} defaultValue - Default value to return if not a number.
|
|
9
9
|
* @returns {number | DefaultType} value as a number, of defaultValue.
|
|
10
10
|
*/
|
|
11
|
-
export function castToNumber<
|
|
12
|
-
value: unknown,
|
|
13
|
-
defaultValue: DefaultType = undefined
|
|
14
|
-
): number | DefaultType {
|
|
15
|
-
if ( typeof value === 'number' ) {
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if ( typeof value === 'string' ) {
|
|
20
|
-
const float = parseFloat( value );
|
|
21
|
-
if ( ! isNaN( float ) ) {
|
|
22
|
-
return float;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return defaultValue;
|
|
27
|
-
}
|
|
11
|
+
export declare function castToNumber<DefaultType = number>(value: unknown, defaultValue?: DefaultType): number | DefaultType;
|
|
@@ -8,21 +8,4 @@
|
|
|
8
8
|
* @param {DefaultType} defaultValue - Default value to return if not a string
|
|
9
9
|
* @returns {string | DefaultType} value as a string, of defaultValue.
|
|
10
10
|
*/
|
|
11
|
-
export function castToString<
|
|
12
|
-
value: unknown,
|
|
13
|
-
defaultValue: DefaultType | undefined = undefined
|
|
14
|
-
): string | DefaultType | undefined {
|
|
15
|
-
if ( typeof value === 'string' ) {
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if ( ! value ) {
|
|
20
|
-
return defaultValue;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if ( value.toString instanceof Function ) {
|
|
24
|
-
return value.toString();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return defaultValue;
|
|
28
|
-
}
|
|
11
|
+
export declare function castToString<DefaultType = undefined>(value: unknown, defaultValue?: DefaultType | undefined): string | DefaultType | undefined;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* Definition for JSON types:
|
|
5
4
|
* - JSONValue can be any value compatible with JSON; an object (containing JSONValues), array, string, number, boolean, or null
|
|
6
5
|
* - JSONObject is an object containing JSONValues
|
|
7
6
|
* - JSONSchema is a zod schema that can be used to validate JSONValues
|
|
8
7
|
*/
|
|
9
|
-
const literalSchema
|
|
10
|
-
type Literal = z.infer<
|
|
8
|
+
declare const literalSchema: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
|
|
9
|
+
type Literal = z.infer<typeof literalSchema>;
|
|
11
10
|
export type JSONValue = Literal | JSONObject | JSONValue[];
|
|
12
|
-
export type JSONObject = {
|
|
11
|
+
export type JSONObject = {
|
|
12
|
+
[key: string]: JSONValue;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
/* eslint-disable no-use-before-define */
|
|
2
1
|
/**
|
|
3
2
|
* Generic type for handling JSON-like objects.
|
|
4
3
|
*
|
|
5
4
|
* Use this as a last resort if you can't reasonably describe the possible shapes an object can take.
|
|
6
5
|
*/
|
|
7
6
|
export type JSONObject = {
|
|
8
|
-
|
|
7
|
+
[key: string]: JSONValue;
|
|
9
8
|
};
|
|
10
9
|
export type JSONArray = JSONValue[];
|
|
11
10
|
export type JSONValue = string | number | boolean | JSONObject | JSONArray | null | undefined;
|
|
12
|
-
|
|
13
11
|
/**
|
|
14
12
|
* Returns true if the given JSONValue is a JSONObject.
|
|
15
13
|
*
|
|
16
14
|
* @param {JSONValue} value - Value to check.
|
|
17
15
|
* @returns {boolean} True if the given value is a JSONObject.
|
|
18
16
|
*/
|
|
19
|
-
export function isJsonObject(
|
|
20
|
-
return !! value && value instanceof Object && ! ( value instanceof Array );
|
|
21
|
-
}
|
|
22
|
-
|
|
17
|
+
export declare function isJsonObject(value: JSONValue): value is JSONObject;
|
|
23
18
|
/**
|
|
24
19
|
* Returns true if the given JSONValue is a JSONArray.
|
|
25
20
|
* Sure, you could use x instanceof Array but this is shorter and more consistent.
|
|
@@ -27,6 +22,4 @@ export function isJsonObject( value: JSONValue ): value is JSONObject {
|
|
|
27
22
|
* @param {JSONValue} value - Value to check.
|
|
28
23
|
* @returns {boolean} True if the given value is a JSONArray.
|
|
29
24
|
*/
|
|
30
|
-
export function isJsonArray(
|
|
31
|
-
return value instanceof Array;
|
|
32
|
-
}
|
|
25
|
+
export declare function isJsonArray(value: JSONValue): value is JSONArray;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type Resolve<RetType = void> = (value: RetType | PromiseLike<RetType>) => void;
|
|
2
|
+
type PollPromiseArgs<RetType = void> = {
|
|
3
|
+
interval: number;
|
|
4
|
+
timeout: number;
|
|
5
|
+
timeoutError?: string;
|
|
6
|
+
callback: (resolve: Resolve<RetType>) => Promise<void> | void;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Repeatedly poll callback every <interval> milliseconds until it calls the
|
|
10
|
+
* resolve() callback. If the callback throws an error, the whole polling
|
|
11
|
+
* promise will reject.
|
|
12
|
+
*
|
|
13
|
+
* Rejects with a timeout after <timeout> milliseconds.
|
|
14
|
+
*
|
|
15
|
+
* @template RetType
|
|
16
|
+
* @param {object} obj - Arguments object.
|
|
17
|
+
* @param {number} obj.interval - Milliseconds between calling callback
|
|
18
|
+
* @param {number} obj.timeout - Milliseconds before rejecting w/ a timeout
|
|
19
|
+
* @param {Function} obj.callback - Callback to call every <interval> ms.
|
|
20
|
+
* @param {string} obj.timeoutError - Message to throw on timeout.
|
|
21
|
+
* @returns {Promise< RetType >} - A promise which resolves to the value resolved() inside callback.
|
|
22
|
+
*/
|
|
23
|
+
export default function pollPromise<RetType = void>({ interval, callback, timeout, timeoutError, }: PollPromiseArgs<RetType>): Promise<RetType>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JSONValue } from './json-types';
|
|
2
|
+
/**
|
|
3
|
+
* JavaScript offers no guarantee that caught objects in catch blocks are actually
|
|
4
|
+
* Error objects. This method fixes that, for type safety. :)
|
|
5
|
+
*
|
|
6
|
+
* @param {*} data - Any thrown error data to interpret as an Error (or subclass)
|
|
7
|
+
* @param {JSONValue|Error} defaultMessage - A default message to throw if no sensible error can be found.
|
|
8
|
+
* @returns {Error} the data guaranteed to be an Error or subclass thereof.
|
|
9
|
+
*/
|
|
10
|
+
export declare function standardizeError(data: JSONValue | Error, defaultMessage?: string): Error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-boost-score-api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "A package to get the Jetpack Boost score of a site",
|
|
5
5
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/boost-score-api/#readme",
|
|
6
6
|
"bugs": {
|
package/.gitattributes
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Files not needed to be distributed in the package.
|
|
2
|
-
.gitattributes export-ignore
|
|
3
|
-
node_modules export-ignore
|
|
4
|
-
package.json export-ignore
|
|
5
|
-
|
|
6
|
-
/build/** production-exclude
|
|
7
|
-
|
|
8
|
-
# Files to exclude from the mirror repo
|
|
9
|
-
/changelog/** production-exclude
|
|
10
|
-
/.eslintrc.cjs production-exclude
|
|
11
|
-
.gitignore production-exclude
|
|
12
|
-
tests/** production-exclude
|
|
13
|
-
babel.config.js production-exclude
|
|
14
|
-
webpack.config.js production-exclude
|
|
15
|
-
tsconfig.json production-exclude
|
|
16
|
-
|
package/babel.config.cjs
DELETED
package/src/api-error.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { __, sprintf } from '@wordpress/i18n';
|
|
2
|
-
import { castToString } from './utils/cast-to-string';
|
|
3
|
-
import { isJsonObject } from './utils/json-types';
|
|
4
|
-
import type { JSONObject } from './utils/json-object-type';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Special error subclass returned by API Calls with extra
|
|
8
|
-
* information.
|
|
9
|
-
*/
|
|
10
|
-
export class ApiError extends Error {
|
|
11
|
-
public constructor(
|
|
12
|
-
public readonly httpCode: number,
|
|
13
|
-
public readonly body: JSONObject | string | null,
|
|
14
|
-
public readonly parseError: Error | null
|
|
15
|
-
) {
|
|
16
|
-
super();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Override Error.message to generate a message based on http code and json body.
|
|
20
|
-
get message(): string {
|
|
21
|
-
switch ( this.httpCode ) {
|
|
22
|
-
case 403: {
|
|
23
|
-
return this.getRestApiErrorMessage();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// For HTTP 200 responses, look for JSON parsing issues.
|
|
27
|
-
case 200: {
|
|
28
|
-
if ( this.parseError ) {
|
|
29
|
-
return sprintf(
|
|
30
|
-
/* Translators: %s refers to a browser-supplied error message (hopefully already in the right language) */
|
|
31
|
-
__(
|
|
32
|
-
'Received invalid response while communicating with your WordPress site: %s',
|
|
33
|
-
'boost-score-api'
|
|
34
|
-
),
|
|
35
|
-
this.parseError.message
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return sprintf(
|
|
44
|
-
/* Translators: %d refers to numeric HTTP error code */
|
|
45
|
-
__( 'HTTP %d error received while communicating with the server.', 'boost-score-api' ),
|
|
46
|
-
this.httpCode
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Returns the body of this in a string format for display. Pretty printed JSON if valid, raw dump if not.
|
|
51
|
-
public getDisplayBody(): string {
|
|
52
|
-
if ( isJsonObject( this.body ) ) {
|
|
53
|
-
return JSON.stringify( this.body, null, ' ' );
|
|
54
|
-
}
|
|
55
|
-
return castToString( this.body, '' ).substring( 0, 1000 );
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Returns an error message appropriate for a site whose API doesn't seem to be available.
|
|
59
|
-
getRestApiErrorMessage(): string {
|
|
60
|
-
return __(
|
|
61
|
-
"Your site's REST API does not seem to be accessible. Jetpack Boost requires access to your REST API in order to receive site performance scores. Please make sure that your site's REST API is active and accessible, and try again.",
|
|
62
|
-
'boost-score-api'
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
package/src/api.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { __, sprintf } from '@wordpress/i18n';
|
|
2
|
-
import { ApiError } from './api-error';
|
|
3
|
-
import { JETPACK_BOOST_REST_NAMESPACE, JETPACK_BOOST_REST_PREFIX } from './config';
|
|
4
|
-
import type { JSONObject } from './utils/json-object-type';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get the full URL to an endpoint.
|
|
8
|
-
*
|
|
9
|
-
* @param {string} path - The path to the endpoint.
|
|
10
|
-
* @param {string} root - The root URL to use.
|
|
11
|
-
* @returns {string} - The full URL.
|
|
12
|
-
*/
|
|
13
|
-
function getEndpointUrl( path: string, root: string ): string {
|
|
14
|
-
return root + JETPACK_BOOST_REST_NAMESPACE + JETPACK_BOOST_REST_PREFIX + path;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Send a request to the Boost REST API.
|
|
19
|
-
*
|
|
20
|
-
* @param {string} method - The HTTP method to use.
|
|
21
|
-
* @param {string} root - The root URL to use.
|
|
22
|
-
* @param {string} path - The path to the endpoint.
|
|
23
|
-
* @param {null | JSONObject} body - The body of the request.
|
|
24
|
-
* @param {string} nonce - The nonce to use.
|
|
25
|
-
* @returns {Promise} - The response.
|
|
26
|
-
*/
|
|
27
|
-
async function sendRequest(
|
|
28
|
-
method: string,
|
|
29
|
-
root: string,
|
|
30
|
-
path: string,
|
|
31
|
-
body: null | JSONObject = null,
|
|
32
|
-
nonce: string
|
|
33
|
-
): Promise< Response > {
|
|
34
|
-
const args: JSONObject = {
|
|
35
|
-
method,
|
|
36
|
-
mode: 'cors',
|
|
37
|
-
headers: {
|
|
38
|
-
'X-WP-Nonce': nonce,
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
if ( ( 'post' === method || 'delete' === method ) && body ) {
|
|
43
|
-
args.body = JSON.stringify( body );
|
|
44
|
-
args.headers[ 'Content-Type' ] = 'application/json';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const endpointFullUrl = getEndpointUrl( path, root );
|
|
48
|
-
let apiCall: Response;
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
apiCall = await fetch( endpointFullUrl, args );
|
|
52
|
-
} catch ( error ) {
|
|
53
|
-
const cleanupArgs = args;
|
|
54
|
-
delete cleanupArgs.body;
|
|
55
|
-
delete cleanupArgs.headers[ 'X-WP-Nonce' ];
|
|
56
|
-
const errorInfo = {
|
|
57
|
-
requestInitiator: window.location.href,
|
|
58
|
-
requestUrl: endpointFullUrl,
|
|
59
|
-
requestArgs: cleanupArgs,
|
|
60
|
-
originalErrorMessage: error.toString(),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Throwing again an error so it can be caught higher up and displayed in the UI.
|
|
64
|
-
throw new Error(
|
|
65
|
-
sprintf(
|
|
66
|
-
/* Translators: %s refers to a string representation of an error object containing useful debug information */
|
|
67
|
-
__(
|
|
68
|
-
'An error occurred while trying to communicate with the site REST API. Extra debug info: %s',
|
|
69
|
-
'boost-score-api'
|
|
70
|
-
),
|
|
71
|
-
JSON.stringify( errorInfo )
|
|
72
|
-
)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return apiCall;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Make a request to the Boost REST API.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} method - The HTTP method to use.
|
|
83
|
-
* @param {string} root - The root URL to use.
|
|
84
|
-
* @param {string} path - The path to the endpoint.
|
|
85
|
-
* @param {null | JSONObject} body - The body of the request.
|
|
86
|
-
* @param {string} nonce - The nonce to use.
|
|
87
|
-
* @returns {Promise} - The response.
|
|
88
|
-
*/
|
|
89
|
-
async function makeRequest< T = JSONObject >(
|
|
90
|
-
method: string,
|
|
91
|
-
root: string,
|
|
92
|
-
path: string,
|
|
93
|
-
body: null | JSONObject = null,
|
|
94
|
-
nonce: string
|
|
95
|
-
): Promise< T > {
|
|
96
|
-
const response = await sendRequest( method, root, path, body, nonce );
|
|
97
|
-
|
|
98
|
-
// Fetch response as text.
|
|
99
|
-
let responseBody: string;
|
|
100
|
-
try {
|
|
101
|
-
responseBody = await response.text();
|
|
102
|
-
} catch ( err ) {
|
|
103
|
-
throw new ApiError( response.status, null, err );
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Try to parse it as JSON, catch errors explicitly.
|
|
107
|
-
let jsonBody: JSONObject;
|
|
108
|
-
try {
|
|
109
|
-
jsonBody = JSON.parse( responseBody );
|
|
110
|
-
} catch ( err ) {
|
|
111
|
-
throw new ApiError( response.status, responseBody, err );
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Throw an error if not HTTP 200.
|
|
115
|
-
if ( ! response.ok ) {
|
|
116
|
-
throw new ApiError( response.status, jsonBody, null );
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
120
|
-
return jsonBody as any;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Make a GET request to the Boost REST API.
|
|
125
|
-
*
|
|
126
|
-
* @param {string} root - The root URL to use.
|
|
127
|
-
* @param {string} path - The path to the endpoint.
|
|
128
|
-
* @param {string} nonce - The nonce to use.
|
|
129
|
-
* @returns {Promise} - The response.
|
|
130
|
-
*/
|
|
131
|
-
function get< T = JSONObject >( root: string, path: string, nonce: string ): Promise< T > {
|
|
132
|
-
return makeRequest< T >( 'get', root, path, null, nonce );
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Make a POST request to the Boost REST API.
|
|
137
|
-
*
|
|
138
|
-
* @param {string} root - The root URL to use.
|
|
139
|
-
* @param {string} path - The path to the endpoint.
|
|
140
|
-
* @param {null | JSONObject} body - The body of the request.
|
|
141
|
-
* @param {string} nonce - The nonce to use.
|
|
142
|
-
* @returns {Promise} - The response.
|
|
143
|
-
*/
|
|
144
|
-
function post< T = JSONObject >(
|
|
145
|
-
root: string,
|
|
146
|
-
path: string,
|
|
147
|
-
body: JSONObject | null = null,
|
|
148
|
-
nonce: string
|
|
149
|
-
): Promise< T > {
|
|
150
|
-
return makeRequest< T >( 'post', root, path, body, nonce );
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export default {
|
|
154
|
-
get,
|
|
155
|
-
post,
|
|
156
|
-
};
|
package/src/config.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import { __ } from '@wordpress/i18n';
|
|
2
|
-
import api from './api';
|
|
3
|
-
import { castToNumber } from './utils/cast-to-number';
|
|
4
|
-
import { castToString } from './utils/cast-to-string';
|
|
5
|
-
import { isJsonObject, JSONObject } from './utils/json-types';
|
|
6
|
-
import pollPromise from './utils/poll-promise';
|
|
7
|
-
import { standardizeError } from './utils/standardize-error';
|
|
8
|
-
|
|
9
|
-
const pollTimeout = 2 * 60 * 1000;
|
|
10
|
-
const pollInterval = 5 * 1000;
|
|
11
|
-
|
|
12
|
-
type SpeedScores = {
|
|
13
|
-
mobile: number;
|
|
14
|
-
desktop: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type SpeedScoresSet = {
|
|
18
|
-
current: SpeedScores;
|
|
19
|
-
noBoost: SpeedScores;
|
|
20
|
-
isStale: boolean;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
interface ScoreValue {
|
|
24
|
-
score: number;
|
|
25
|
-
value: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface SpeedHistoryDimension {
|
|
29
|
-
mobile_overall_score: number;
|
|
30
|
-
desktop_overall_score: number;
|
|
31
|
-
mobile_lcp: ScoreValue;
|
|
32
|
-
desktop_lcp: ScoreValue;
|
|
33
|
-
mobile_tbt: ScoreValue;
|
|
34
|
-
desktop_tbt: ScoreValue;
|
|
35
|
-
mobile_cls: ScoreValue;
|
|
36
|
-
desktop_cls: ScoreValue;
|
|
37
|
-
mobile_si: ScoreValue;
|
|
38
|
-
desktop_si: ScoreValue;
|
|
39
|
-
mobile_tti: ScoreValue;
|
|
40
|
-
desktop_tti: ScoreValue;
|
|
41
|
-
mobile_fcp: ScoreValue;
|
|
42
|
-
desktop_fcp: ScoreValue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface SpeedHistoryPeriod {
|
|
46
|
-
timestamp: number;
|
|
47
|
-
dimensions: SpeedHistoryDimension[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface SpeedHistoryResponse {
|
|
51
|
-
data: {
|
|
52
|
-
_meta: {
|
|
53
|
-
start: number;
|
|
54
|
-
end: number;
|
|
55
|
-
};
|
|
56
|
-
periods: SpeedHistoryPeriod[];
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
type ParsedApiResponse = {
|
|
61
|
-
status: string;
|
|
62
|
-
scores?: SpeedScoresSet;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Kick off a request to generate speed scores for this site. Will automatically
|
|
67
|
-
* poll for a response until the task is done, returning a SpeedScores object.
|
|
68
|
-
*
|
|
69
|
-
* @param {boolean} force - Force regenerate speed scores.
|
|
70
|
-
* @param {string} rootUrl - Root URL for the HTTP request.
|
|
71
|
-
* @param {string} siteUrl - URL of the site.
|
|
72
|
-
* @param {string} nonce - Nonce to use for authentication.
|
|
73
|
-
* @returns {SpeedScoresSet} Speed scores returned by the server.
|
|
74
|
-
*/
|
|
75
|
-
export async function requestSpeedScores(
|
|
76
|
-
force = false,
|
|
77
|
-
rootUrl: string,
|
|
78
|
-
siteUrl: string,
|
|
79
|
-
nonce: string
|
|
80
|
-
): Promise< SpeedScoresSet > {
|
|
81
|
-
// Request metrics
|
|
82
|
-
const response = parseResponse(
|
|
83
|
-
await api.post(
|
|
84
|
-
rootUrl,
|
|
85
|
-
force ? '/speed-scores/refresh' : '/speed-scores',
|
|
86
|
-
{ url: siteUrl },
|
|
87
|
-
nonce
|
|
88
|
-
)
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// If the response contains ready-to-use metrics, we're done here.
|
|
92
|
-
if ( response.scores ) {
|
|
93
|
-
return response.scores;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Poll for metrics.
|
|
97
|
-
return await pollRequest( rootUrl, siteUrl, nonce );
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Get SpeedScores gistory to render the Graph. Will automatically
|
|
102
|
-
* poll for a response until the task is done, returning a SpeedHistory object.
|
|
103
|
-
*
|
|
104
|
-
* @param {string} rootUrl - Root URL for the HTTP request.
|
|
105
|
-
* @param {string} siteUrl - URL of the site.
|
|
106
|
-
* @param {string} nonce - Nonce to use for authentication.
|
|
107
|
-
* @returns {SpeedHistoryResponse} Speed score history returned by the server.
|
|
108
|
-
*/
|
|
109
|
-
export async function requestSpeedScoresHistory(
|
|
110
|
-
rootUrl: string,
|
|
111
|
-
siteUrl: string,
|
|
112
|
-
nonce: string
|
|
113
|
-
): Promise< SpeedHistoryResponse > {
|
|
114
|
-
const end = new Date().getTime();
|
|
115
|
-
const start = end - 1000 * 60 * 60 * 24 * 30; // 30 days ago
|
|
116
|
-
// Request metrics
|
|
117
|
-
const response = await api.post< SpeedHistoryResponse >(
|
|
118
|
-
rootUrl,
|
|
119
|
-
'/speed-scores-history',
|
|
120
|
-
{ start, end },
|
|
121
|
-
nonce
|
|
122
|
-
);
|
|
123
|
-
return response;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Helper method for parsing a response from a speed score API request. Returns
|
|
128
|
-
* scores (if ready), and a status (success|pending|error).
|
|
129
|
-
*
|
|
130
|
-
* @param {JSONObject} response - API response to parse
|
|
131
|
-
* @returns {ParsedApiResponse} API response, processed.
|
|
132
|
-
*/
|
|
133
|
-
function parseResponse( response: JSONObject ): ParsedApiResponse {
|
|
134
|
-
// Handle an explicit error
|
|
135
|
-
if ( response.error ) {
|
|
136
|
-
const defaultErrorMessage = __(
|
|
137
|
-
'An unknown error occurred while requesting metrics',
|
|
138
|
-
'boost-score-api'
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
throw standardizeError( response.error, defaultErrorMessage );
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Check if ready.
|
|
145
|
-
if ( isJsonObject( response.scores ) ) {
|
|
146
|
-
return {
|
|
147
|
-
status: 'success',
|
|
148
|
-
scores: {
|
|
149
|
-
current: isJsonObject( response.scores.current )
|
|
150
|
-
? {
|
|
151
|
-
mobile: castToNumber( response.scores.current.mobile, 0 ),
|
|
152
|
-
desktop: castToNumber( response.scores.current.desktop, 0 ),
|
|
153
|
-
}
|
|
154
|
-
: {
|
|
155
|
-
mobile: 0,
|
|
156
|
-
desktop: 0,
|
|
157
|
-
},
|
|
158
|
-
noBoost: isJsonObject( response.scores.noBoost )
|
|
159
|
-
? {
|
|
160
|
-
mobile: castToNumber( response.scores.noBoost.mobile, 0 ),
|
|
161
|
-
desktop: castToNumber( response.scores.noBoost.desktop, 0 ),
|
|
162
|
-
}
|
|
163
|
-
: null,
|
|
164
|
-
isStale: !! response.scores.isStale,
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const requestStatus = castToString( response.status );
|
|
170
|
-
if ( ! requestStatus ) {
|
|
171
|
-
throw new Error( __( 'Invalid response while requesting metrics', 'boost-score-api' ) );
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
status: requestStatus,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Poll a speed score request for results, timing out if it takes too long.
|
|
181
|
-
*
|
|
182
|
-
* @param {string} rootUrl - Root URL of the site to request metrics for
|
|
183
|
-
* @param {string} siteUrl - Site URL to request metrics for
|
|
184
|
-
* @param {string} nonce - Nonce to use for authentication
|
|
185
|
-
* @returns {SpeedScoresSet} Speed scores returned by the server.
|
|
186
|
-
*/
|
|
187
|
-
async function pollRequest(
|
|
188
|
-
rootUrl: string,
|
|
189
|
-
siteUrl: string,
|
|
190
|
-
nonce: string
|
|
191
|
-
): Promise< SpeedScoresSet > {
|
|
192
|
-
return pollPromise< SpeedScoresSet >( {
|
|
193
|
-
timeout: pollTimeout,
|
|
194
|
-
interval: pollInterval,
|
|
195
|
-
timeoutError: __( 'Timed out while waiting for speed-score.', 'boost-score-api' ),
|
|
196
|
-
callback: async resolve => {
|
|
197
|
-
const response = parseResponse(
|
|
198
|
-
await api.post( rootUrl, '/speed-scores', { url: siteUrl }, nonce )
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
if ( response.scores ) {
|
|
202
|
-
resolve( response.scores );
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
} );
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Given a mobile and desktop score, return a letter summarizing the overall
|
|
210
|
-
* score.
|
|
211
|
-
*
|
|
212
|
-
* @param {number} mobile - Mobile speed score
|
|
213
|
-
* @param {number} desktop - Desktop speed score
|
|
214
|
-
* @returns {string} letter score
|
|
215
|
-
*/
|
|
216
|
-
export function getScoreLetter( mobile: number, desktop: number ): string {
|
|
217
|
-
const sum = mobile + desktop;
|
|
218
|
-
const averageScore = sum / 2;
|
|
219
|
-
|
|
220
|
-
if ( averageScore > 90 ) {
|
|
221
|
-
return 'A';
|
|
222
|
-
}
|
|
223
|
-
if ( averageScore > 75 ) {
|
|
224
|
-
return 'B';
|
|
225
|
-
}
|
|
226
|
-
if ( averageScore > 50 ) {
|
|
227
|
-
return 'C';
|
|
228
|
-
}
|
|
229
|
-
if ( averageScore > 35 ) {
|
|
230
|
-
return 'D';
|
|
231
|
-
}
|
|
232
|
-
if ( averageScore > 25 ) {
|
|
233
|
-
return 'E';
|
|
234
|
-
}
|
|
235
|
-
return 'F';
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Find out if site scores changed. We fire a popout modal if they improve or worsen.
|
|
240
|
-
* The message varies depending on the results of the speed scores so lets modify this
|
|
241
|
-
*
|
|
242
|
-
* @param {SpeedScoresSet} scores - Speed scores returned by the server.
|
|
243
|
-
* @returns {boolean} true if scores changed.
|
|
244
|
-
*/
|
|
245
|
-
export function didScoresChange( scores: SpeedScoresSet ): boolean {
|
|
246
|
-
const current = scores.current;
|
|
247
|
-
const noBoost = scores.noBoost;
|
|
248
|
-
|
|
249
|
-
// lets make this a little bit more readable. If one of the scores is null.
|
|
250
|
-
// then the scores haven't changed. So return false.
|
|
251
|
-
if ( null == current || null == noBoost ) {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// if either the mobile or the desktop scores have changed. Return true.
|
|
256
|
-
if ( current.mobile !== noBoost.mobile || current.desktop !== noBoost.desktop ) {
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
//else if reach here then the scores are the same.
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Determine the change in scores to pass through to other functions.
|
|
266
|
-
*
|
|
267
|
-
* @param {SpeedScoresSet} scores - Speed scores returned by the server.
|
|
268
|
-
* @returns {number} - The change in scores in percentage.
|
|
269
|
-
*/
|
|
270
|
-
export function getScoreMovementPercentage( scores: SpeedScoresSet ): number {
|
|
271
|
-
const current = scores.current;
|
|
272
|
-
const noBoost = scores.noBoost;
|
|
273
|
-
let currentScore = 0;
|
|
274
|
-
let noBoostScore = 0;
|
|
275
|
-
|
|
276
|
-
if ( current !== null && noBoost !== null ) {
|
|
277
|
-
currentScore = scores.current.mobile + scores.current.desktop;
|
|
278
|
-
noBoostScore = scores.noBoost.mobile + scores.noBoost.desktop;
|
|
279
|
-
const change = currentScore / noBoostScore - 1;
|
|
280
|
-
return Math.round( change * 100 );
|
|
281
|
-
}
|
|
282
|
-
return 0;
|
|
283
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { __ } from '@wordpress/i18n';
|
|
2
|
-
|
|
3
|
-
type Resolve< RetType = void > = ( value: RetType | PromiseLike< RetType > ) => void;
|
|
4
|
-
|
|
5
|
-
type PollPromiseArgs< RetType = void > = {
|
|
6
|
-
interval: number;
|
|
7
|
-
timeout: number;
|
|
8
|
-
timeoutError?: string;
|
|
9
|
-
callback: ( resolve: Resolve< RetType > ) => Promise< void > | void;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Repeatedly poll callback every <interval> milliseconds until it calls the
|
|
14
|
-
* resolve() callback. If the callback throws an error, the whole polling
|
|
15
|
-
* promise will reject.
|
|
16
|
-
*
|
|
17
|
-
* Rejects with a timeout after <timeout> milliseconds.
|
|
18
|
-
*
|
|
19
|
-
* @template RetType
|
|
20
|
-
* @param {object} obj - Arguments object.
|
|
21
|
-
* @param {number} obj.interval - Milliseconds between calling callback
|
|
22
|
-
* @param {number} obj.timeout - Milliseconds before rejecting w/ a timeout
|
|
23
|
-
* @param {Function} obj.callback - Callback to call every <interval> ms.
|
|
24
|
-
* @param {string} obj.timeoutError - Message to throw on timeout.
|
|
25
|
-
* @returns {Promise< RetType >} - A promise which resolves to the value resolved() inside callback.
|
|
26
|
-
*/
|
|
27
|
-
export default async function pollPromise< RetType = void >( {
|
|
28
|
-
interval,
|
|
29
|
-
callback,
|
|
30
|
-
timeout,
|
|
31
|
-
timeoutError,
|
|
32
|
-
}: PollPromiseArgs< RetType > ): Promise< RetType > {
|
|
33
|
-
let timeoutHandle: ReturnType< typeof setTimeout >,
|
|
34
|
-
intervalHandle: ReturnType< typeof setInterval >;
|
|
35
|
-
|
|
36
|
-
return new Promise< RetType >( ( resolve, reject ) => {
|
|
37
|
-
timeoutHandle = setTimeout( () => {
|
|
38
|
-
reject( new Error( timeoutError || __( 'Timed out', 'boost-score-api' ) ) );
|
|
39
|
-
}, timeout || 2 * 60 * 1000 );
|
|
40
|
-
|
|
41
|
-
intervalHandle = setInterval( async () => {
|
|
42
|
-
try {
|
|
43
|
-
await Promise.resolve( callback( resolve ) );
|
|
44
|
-
} catch ( err ) {
|
|
45
|
-
reject( err );
|
|
46
|
-
}
|
|
47
|
-
}, interval );
|
|
48
|
-
} ).finally( () => {
|
|
49
|
-
clearTimeout( timeoutHandle );
|
|
50
|
-
clearInterval( intervalHandle );
|
|
51
|
-
} );
|
|
52
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { JSONValue } from './json-types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* JavaScript offers no guarantee that caught objects in catch blocks are actually
|
|
5
|
-
* Error objects. This method fixes that, for type safety. :)
|
|
6
|
-
*
|
|
7
|
-
* @param {*} data - Any thrown error data to interpret as an Error (or subclass)
|
|
8
|
-
* @param {JSONValue|Error} defaultMessage - A default message to throw if no sensible error can be found.
|
|
9
|
-
* @returns {Error} the data guaranteed to be an Error or subclass thereof.
|
|
10
|
-
*/
|
|
11
|
-
export function standardizeError( data: JSONValue | Error, defaultMessage?: string ): Error {
|
|
12
|
-
if ( data instanceof Error ) {
|
|
13
|
-
return data;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if ( typeof data === 'string' || data instanceof String ) {
|
|
17
|
-
return new Error( data.toString() );
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
if ( data.message ) {
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
24
|
-
// @ts-ignore
|
|
25
|
-
return new Error( data.message );
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if ( defaultMessage ) {
|
|
29
|
-
return new Error( defaultMessage );
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return new Error( JSON.stringify( data ) );
|
|
33
|
-
}
|
package/webpack.config.cjs
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const path = require( 'path' );
|
|
2
|
-
const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpack' );
|
|
3
|
-
|
|
4
|
-
module.exports = {
|
|
5
|
-
entry: './src/index.ts',
|
|
6
|
-
mode: jetpackWebpackConfig.mode,
|
|
7
|
-
devtool: jetpackWebpackConfig.isProduction ? false : 'source-map',
|
|
8
|
-
module: {
|
|
9
|
-
strictExportPresence: true,
|
|
10
|
-
rules: [
|
|
11
|
-
{
|
|
12
|
-
test: /\.ts?$/,
|
|
13
|
-
use: 'ts-loader',
|
|
14
|
-
exclude: /node_modules/,
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
optimization: {
|
|
19
|
-
...jetpackWebpackConfig.optimization,
|
|
20
|
-
},
|
|
21
|
-
resolve: {
|
|
22
|
-
...jetpackWebpackConfig.resolve,
|
|
23
|
-
},
|
|
24
|
-
output: {
|
|
25
|
-
...jetpackWebpackConfig.output,
|
|
26
|
-
path: path.resolve( __dirname, 'build' ),
|
|
27
|
-
filename: 'index.js',
|
|
28
|
-
library: {
|
|
29
|
-
name: 'BoostScoreApiLibrary',
|
|
30
|
-
type: 'umd',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
plugins: [ ...jetpackWebpackConfig.StandardPlugins() ],
|
|
34
|
-
};
|