hevy-shared 1.0.807 → 1.0.809
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/built/API/APIClient.d.ts +122 -0
- package/built/API/APIClient.js +303 -0
- package/built/API/index.d.ts +2 -0
- package/built/API/index.js +18 -0
- package/built/API/types.d.ts +35 -0
- package/built/API/types.js +18 -0
- package/built/async.d.ts +11 -0
- package/built/async.js +48 -0
- package/built/index.d.ts +13 -2
- package/built/typeUtils.d.ts +3 -0
- package/package.json +13 -2
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ClientAuthToken, DeepReadonly } from '..';
|
|
2
|
+
import { RequestConfig, HTTPResponse, HTTPClient, HTTPErrorHandler } from './types';
|
|
3
|
+
interface HevyAPIClientConfig {
|
|
4
|
+
/**
|
|
5
|
+
* How long before predicted token expiry to request a new token. We request
|
|
6
|
+
* new tokens a little bit before they expire, so that we don't have to wait
|
|
7
|
+
* for the backend to send us a 401 first. We do it pre-emptively so that we
|
|
8
|
+
* wouldn't need to wait for requests to fail first, and then retry them.
|
|
9
|
+
*
|
|
10
|
+
* Optional.
|
|
11
|
+
*
|
|
12
|
+
* @default {1 minute}
|
|
13
|
+
*/
|
|
14
|
+
readonly tokenExpirySafetyThresholdMs?: number;
|
|
15
|
+
/**
|
|
16
|
+
* How long we expect the access token to be valid for. Used to override the
|
|
17
|
+
* expiry time sent by the backend in case the client clock is in the future
|
|
18
|
+
* and the client thinks that the token will expire sooner than it actually
|
|
19
|
+
* will. Even if this assumption ever stops being correct, the client will be
|
|
20
|
+
* able to self-correct by receiving an `AccessTokenExpired` 401 response.
|
|
21
|
+
*
|
|
22
|
+
* Optional.
|
|
23
|
+
*
|
|
24
|
+
* @default {15 minutes}
|
|
25
|
+
*/
|
|
26
|
+
readonly accessTokenMinimumValidAgeMs?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Minimum time between successive calls to the `auth/refresh_token` endpoint
|
|
29
|
+
* in case it doesn't return an error. Also used to deal with potential clock
|
|
30
|
+
* issues on the client side, or some unforeseen issue on the backend, as the
|
|
31
|
+
* last line of defence. No matter what may happen, we absolutely never ever
|
|
32
|
+
* want to end up accidentally spamming this endpoint because it could have a
|
|
33
|
+
* cascading clusterfuck effect on all the other requests.
|
|
34
|
+
*
|
|
35
|
+
* Optional.
|
|
36
|
+
*
|
|
37
|
+
* @default {20 seconds}
|
|
38
|
+
*/
|
|
39
|
+
readonly tokenRefreshThrottleMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* The API endpoint used to refresh the auth tokens using a refresh token.
|
|
42
|
+
* The URL is relative to the backend base URL.
|
|
43
|
+
*
|
|
44
|
+
* Optional.
|
|
45
|
+
*
|
|
46
|
+
* @default {POST /auth/refresh_token}
|
|
47
|
+
*/
|
|
48
|
+
readonly refreshAuthTokenApiEndpoint?: {
|
|
49
|
+
readonly method: 'post';
|
|
50
|
+
readonly url: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Callback to use when receiving a new auth token. Used to send it to the
|
|
54
|
+
* consumer of the class to save the new token to its local storage.
|
|
55
|
+
*
|
|
56
|
+
* Required.
|
|
57
|
+
*/
|
|
58
|
+
onNewAuthToken(newAuthToken: ClientAuthToken): void;
|
|
59
|
+
/**
|
|
60
|
+
* Callback to use when receiving an HTTP error response from the backend, to
|
|
61
|
+
* determine whether it is a response indicating that the token has expired.
|
|
62
|
+
*
|
|
63
|
+
* Optional.
|
|
64
|
+
*
|
|
65
|
+
* @default ...
|
|
66
|
+
*/
|
|
67
|
+
isAccessTokenExpiredResponse?(response: HTTPResponse<unknown>): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Callback to use when receiving an HTTP error response from the backend, to
|
|
70
|
+
* determine whether it is a response indicating that the token is invalid.
|
|
71
|
+
*
|
|
72
|
+
* Optional.
|
|
73
|
+
*
|
|
74
|
+
* @default ...
|
|
75
|
+
*/
|
|
76
|
+
isAccessTokenInvalidResponse?(response: HTTPResponse<unknown>): boolean;
|
|
77
|
+
}
|
|
78
|
+
export declare class HevyAPIClient {
|
|
79
|
+
private static readonly DEFAULT_TOKEN_EXPIRY_SAFETY_THRESHOLD_MS;
|
|
80
|
+
private static readonly DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS;
|
|
81
|
+
private static readonly DEFAULT_TOKEN_REFRESH_THROTTLE_MS;
|
|
82
|
+
private static readonly DEFAULT_REFRESH_AUTH_TOKEN_API_ENDPOINT;
|
|
83
|
+
private readonly _config;
|
|
84
|
+
private _errorHandlers;
|
|
85
|
+
private _authToken;
|
|
86
|
+
private _legacyAuthToken;
|
|
87
|
+
private _httpClient;
|
|
88
|
+
private _lastTokenRefresh;
|
|
89
|
+
constructor(httpClient: HTTPClient, config: HevyAPIClientConfig);
|
|
90
|
+
private get _authHeaders();
|
|
91
|
+
private _addAuthHeaders;
|
|
92
|
+
private refreshExpiredAuthToken;
|
|
93
|
+
private forceRefreshAuthToken;
|
|
94
|
+
private waitForTokenRefresh;
|
|
95
|
+
private get _isTokenRecentlyRefreshed();
|
|
96
|
+
private _refreshAuthToken;
|
|
97
|
+
private _handleResponse;
|
|
98
|
+
isAccessTokenExpiredResponse(response: HTTPResponse<unknown>): boolean;
|
|
99
|
+
isAccessTokenInvalidResponse(response: HTTPResponse<unknown>): boolean;
|
|
100
|
+
get refreshAuthTokenApiEndpoint(): {
|
|
101
|
+
readonly method: "post";
|
|
102
|
+
readonly url: string;
|
|
103
|
+
};
|
|
104
|
+
setAuthToken(newAuthToken: DeepReadonly<{
|
|
105
|
+
authToken: ClientAuthToken | null;
|
|
106
|
+
legacyAuthToken: string | null;
|
|
107
|
+
}>): Promise<void>;
|
|
108
|
+
clearAuthToken(): Promise<void>;
|
|
109
|
+
attachErrorHandler(onError: HTTPErrorHandler<{
|
|
110
|
+
willRetry: boolean;
|
|
111
|
+
isTokenRefreshedAfterRequest: boolean;
|
|
112
|
+
}>): void;
|
|
113
|
+
removeErrorHandlers(): void;
|
|
114
|
+
get<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
115
|
+
delete<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
116
|
+
head<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
117
|
+
options<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
118
|
+
post<T = never, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
119
|
+
put<T = never, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
120
|
+
patch<T = never, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
121
|
+
}
|
|
122
|
+
export {};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.HevyAPIClient = void 0;
|
|
19
|
+
const __1 = require("..");
|
|
20
|
+
const types_1 = require("./types");
|
|
21
|
+
class HevyAPIClient {
|
|
22
|
+
constructor(httpClient, config) {
|
|
23
|
+
this._errorHandlers = [];
|
|
24
|
+
this._authToken = null;
|
|
25
|
+
this._legacyAuthToken = null;
|
|
26
|
+
this._addAuthHeaders = (config) => (Object.assign(Object.assign({}, config), { headers: Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.headers), this._authHeaders) }));
|
|
27
|
+
this._httpClient = httpClient;
|
|
28
|
+
this._config = Object.assign({ tokenExpirySafetyThresholdMs: HevyAPIClient.DEFAULT_TOKEN_EXPIRY_SAFETY_THRESHOLD_MS, accessTokenMinimumValidAgeMs: HevyAPIClient.DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS, tokenRefreshThrottleMs: HevyAPIClient.DEFAULT_TOKEN_REFRESH_THROTTLE_MS, refreshAuthTokenApiEndpoint: HevyAPIClient.DEFAULT_REFRESH_AUTH_TOKEN_API_ENDPOINT, isAccessTokenExpiredResponse: (response) => response.status === 401 &&
|
|
29
|
+
(0, types_1.isHTTPErrorResponse)(response) &&
|
|
30
|
+
response.data.error === 'AccessTokenExpired', isAccessTokenInvalidResponse: (response) => response.status === 401 &&
|
|
31
|
+
(0, types_1.isHTTPErrorResponse)(response) &&
|
|
32
|
+
response.data.error === 'InvalidAccessToken' }, config);
|
|
33
|
+
const classProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter((m) => m in this && typeof m === 'string' && m !== 'constructor');
|
|
34
|
+
classProperties.forEach((m) => {
|
|
35
|
+
if (typeof this[m] === 'function')
|
|
36
|
+
this[m] = this[m].bind(this);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
get _authHeaders() {
|
|
40
|
+
return this._authToken
|
|
41
|
+
? { authorization: `Bearer ${this._authToken.access_token}` }
|
|
42
|
+
: this._legacyAuthToken
|
|
43
|
+
? { 'auth-token': this._legacyAuthToken }
|
|
44
|
+
: null;
|
|
45
|
+
}
|
|
46
|
+
refreshExpiredAuthToken() {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const { _authToken } = this;
|
|
49
|
+
if (!_authToken)
|
|
50
|
+
return;
|
|
51
|
+
if (new Date().valueOf() <=
|
|
52
|
+
new Date(_authToken.expires_at).valueOf() -
|
|
53
|
+
this._config.tokenExpirySafetyThresholdMs) {
|
|
54
|
+
// Token is still valid, at least according to the client. Don't refresh.
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
yield this._refreshAuthToken(_authToken);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
forceRefreshAuthToken() {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const { _authToken } = this;
|
|
63
|
+
if (!_authToken)
|
|
64
|
+
return;
|
|
65
|
+
yield this._refreshAuthToken(_authToken);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
waitForTokenRefresh() {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
// This may look like it does nothing, but that is absolutely not the case.
|
|
71
|
+
// Do not remove this function!
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
get _isTokenRecentlyRefreshed() {
|
|
75
|
+
const { _lastTokenRefresh } = this;
|
|
76
|
+
return (_lastTokenRefresh !== undefined &&
|
|
77
|
+
_lastTokenRefresh.valueOf() >
|
|
78
|
+
new Date().valueOf() - this._config.tokenRefreshThrottleMs);
|
|
79
|
+
}
|
|
80
|
+
_refreshAuthToken(authToken) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
if (this._isTokenRecentlyRefreshed) {
|
|
83
|
+
// We have called this function recently. Do not ever spam this endpoint.
|
|
84
|
+
// Also, if any request needs to be retried due to issues with the client
|
|
85
|
+
// clock being out of sync with the backend, do that with the last token
|
|
86
|
+
// that was obtained after a 401 with the `AccessTokenExpired` response.
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this._lastTokenRefresh = new Date();
|
|
90
|
+
const { access_token, refresh_token } = authToken;
|
|
91
|
+
const { method, url } = this.refreshAuthTokenApiEndpoint;
|
|
92
|
+
// This will throw and bail out if the request fails.
|
|
93
|
+
const response = yield this._handleResponse({
|
|
94
|
+
method,
|
|
95
|
+
url,
|
|
96
|
+
try: () => this._httpClient[method](url, { refresh_token }, { headers: { authorization: `Bearer ${access_token}` } }),
|
|
97
|
+
}, false);
|
|
98
|
+
const newToken = {
|
|
99
|
+
access_token: response.data.access_token,
|
|
100
|
+
refresh_token: response.data.refresh_token,
|
|
101
|
+
expires_at: new Date(response.data.expires_at),
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Do not attempt to auto-refresh the token before this time - by default,
|
|
105
|
+
* {@link HevyAPIClient.DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS 15 mins}
|
|
106
|
+
* after the current time. Normally the backend will generate a token that
|
|
107
|
+
* lives for at least 15 minutes, but since it sends the expiry timestamp
|
|
108
|
+
* rather than a max age in relative time units, even if the client's clock
|
|
109
|
+
* is severely out of sync, we don't want to think that the token has
|
|
110
|
+
* expired when in reality it hasn't.
|
|
111
|
+
*
|
|
112
|
+
* If we ever change the access tokens to really live for less than 15 min,
|
|
113
|
+
* this assertion will no longer hold, and any tokens that are expired
|
|
114
|
+
* despite the client not realising it will be handled by the logic that
|
|
115
|
+
* handles 401s with the `AccessTokenExpired` error response.
|
|
116
|
+
*/
|
|
117
|
+
const earliestAutoRefreshUnixMs = new Date().valueOf() + this._config.accessTokenMinimumValidAgeMs;
|
|
118
|
+
if (newToken.expires_at.valueOf() < earliestAutoRefreshUnixMs) {
|
|
119
|
+
newToken.expires_at = new Date(earliestAutoRefreshUnixMs);
|
|
120
|
+
}
|
|
121
|
+
this._authToken = newToken;
|
|
122
|
+
this._config.onNewAuthToken(newToken);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
_handleResponse(request_1) {
|
|
126
|
+
return __awaiter(this, arguments, void 0, function* (request, retryOnFailure = !this._isTokenRecentlyRefreshed) {
|
|
127
|
+
const requestTime = new Date();
|
|
128
|
+
try {
|
|
129
|
+
const response = yield request.try();
|
|
130
|
+
if (response.status >= 400) {
|
|
131
|
+
throw Object.assign(new Error(`Request failed with status code ${response.status}`), {
|
|
132
|
+
response,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return response;
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
if (!(0, types_1.isHTTPError)(e))
|
|
139
|
+
throw e;
|
|
140
|
+
const { response } = e;
|
|
141
|
+
/**
|
|
142
|
+
* We've had a {@link _refreshAuthToken} called _after_ this request was
|
|
143
|
+
* made. If that request reached the server before this one did, this one
|
|
144
|
+
* will have failed with a 401 because it's using the old token.
|
|
145
|
+
*
|
|
146
|
+
* In this (relatively unlikely) scenario, we will want to retry the
|
|
147
|
+
* request, and also we will not want to log the user out.
|
|
148
|
+
*/
|
|
149
|
+
const isTokenRefreshedAfterRequest = this._lastTokenRefresh !== undefined &&
|
|
150
|
+
requestTime < this._lastTokenRefresh;
|
|
151
|
+
this._errorHandlers.forEach((cb) => cb(response, request, {
|
|
152
|
+
willRetry: retryOnFailure,
|
|
153
|
+
isTokenRefreshedAfterRequest,
|
|
154
|
+
}));
|
|
155
|
+
if (retryOnFailure && this.isAccessTokenExpiredResponse(response)) {
|
|
156
|
+
yield this.forceRefreshAuthToken();
|
|
157
|
+
return yield this._handleResponse(request, false);
|
|
158
|
+
}
|
|
159
|
+
else if (retryOnFailure &&
|
|
160
|
+
this.isAccessTokenInvalidResponse(response) &&
|
|
161
|
+
isTokenRefreshedAfterRequest) {
|
|
162
|
+
yield this.waitForTokenRefresh();
|
|
163
|
+
return yield this._handleResponse(request, false);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
isAccessTokenExpiredResponse(response) {
|
|
172
|
+
return this._config.isAccessTokenExpiredResponse(response);
|
|
173
|
+
}
|
|
174
|
+
isAccessTokenInvalidResponse(response) {
|
|
175
|
+
return this._config.isAccessTokenInvalidResponse(response);
|
|
176
|
+
}
|
|
177
|
+
get refreshAuthTokenApiEndpoint() {
|
|
178
|
+
return Object.assign({}, this._config.refreshAuthTokenApiEndpoint);
|
|
179
|
+
}
|
|
180
|
+
setAuthToken(newAuthToken) {
|
|
181
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
182
|
+
yield this.waitForTokenRefresh();
|
|
183
|
+
const { authToken, legacyAuthToken } = newAuthToken;
|
|
184
|
+
this._authToken = authToken
|
|
185
|
+
? {
|
|
186
|
+
access_token: authToken.access_token,
|
|
187
|
+
refresh_token: authToken.refresh_token,
|
|
188
|
+
expires_at: new Date(authToken.expires_at),
|
|
189
|
+
}
|
|
190
|
+
: null;
|
|
191
|
+
this._legacyAuthToken = legacyAuthToken;
|
|
192
|
+
this._lastTokenRefresh = undefined;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
clearAuthToken() {
|
|
196
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
197
|
+
yield this.waitForTokenRefresh();
|
|
198
|
+
this._authToken = null;
|
|
199
|
+
this._legacyAuthToken = null;
|
|
200
|
+
this._lastTokenRefresh = undefined;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
attachErrorHandler(onError) {
|
|
204
|
+
this._errorHandlers.push(onError);
|
|
205
|
+
}
|
|
206
|
+
removeErrorHandlers() {
|
|
207
|
+
this._errorHandlers.length = 0;
|
|
208
|
+
}
|
|
209
|
+
get(url, config) {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
yield this.refreshExpiredAuthToken();
|
|
212
|
+
const method = 'get';
|
|
213
|
+
return this._handleResponse({
|
|
214
|
+
method,
|
|
215
|
+
url,
|
|
216
|
+
try: () => this._httpClient[method](url, this._addAuthHeaders(config)),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
delete(url, config) {
|
|
221
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
222
|
+
yield this.refreshExpiredAuthToken();
|
|
223
|
+
const method = 'delete';
|
|
224
|
+
return this._handleResponse({
|
|
225
|
+
method,
|
|
226
|
+
url,
|
|
227
|
+
try: () => this._httpClient[method](url, this._addAuthHeaders(config)),
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
head(url, config) {
|
|
232
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
233
|
+
yield this.refreshExpiredAuthToken();
|
|
234
|
+
const method = 'head';
|
|
235
|
+
return this._handleResponse({
|
|
236
|
+
method,
|
|
237
|
+
url,
|
|
238
|
+
try: () => this._httpClient[method](url, this._addAuthHeaders(config)),
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
options(url, config) {
|
|
243
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
yield this.refreshExpiredAuthToken();
|
|
245
|
+
const method = 'options';
|
|
246
|
+
return this._handleResponse({
|
|
247
|
+
method,
|
|
248
|
+
url,
|
|
249
|
+
try: () => this._httpClient[method](url, this._addAuthHeaders(config)),
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
post(url, data, config) {
|
|
254
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
255
|
+
yield this.refreshExpiredAuthToken();
|
|
256
|
+
const method = 'post';
|
|
257
|
+
return this._handleResponse({
|
|
258
|
+
method,
|
|
259
|
+
url,
|
|
260
|
+
try: () => this._httpClient[method](url, data, this._addAuthHeaders(config)),
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
put(url, data, config) {
|
|
265
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
266
|
+
yield this.refreshExpiredAuthToken();
|
|
267
|
+
const method = 'put';
|
|
268
|
+
return this._handleResponse({
|
|
269
|
+
method,
|
|
270
|
+
url,
|
|
271
|
+
try: () => this._httpClient[method](url, data, this._addAuthHeaders(config)),
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
patch(url, data, config) {
|
|
276
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
277
|
+
yield this.refreshExpiredAuthToken();
|
|
278
|
+
const method = 'patch';
|
|
279
|
+
return this._handleResponse({
|
|
280
|
+
method,
|
|
281
|
+
url,
|
|
282
|
+
try: () => this._httpClient[method](url, data, this._addAuthHeaders(config)),
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.HevyAPIClient = HevyAPIClient;
|
|
288
|
+
HevyAPIClient.DEFAULT_TOKEN_EXPIRY_SAFETY_THRESHOLD_MS = 60000;
|
|
289
|
+
HevyAPIClient.DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS = 900000;
|
|
290
|
+
HevyAPIClient.DEFAULT_TOKEN_REFRESH_THROTTLE_MS = 20000;
|
|
291
|
+
HevyAPIClient.DEFAULT_REFRESH_AUTH_TOKEN_API_ENDPOINT = {
|
|
292
|
+
method: 'post',
|
|
293
|
+
url: 'auth/refresh_token',
|
|
294
|
+
};
|
|
295
|
+
__decorate([
|
|
296
|
+
(0, __1.synchronized)(true, 'refreshAuthToken')
|
|
297
|
+
], HevyAPIClient.prototype, "refreshExpiredAuthToken", null);
|
|
298
|
+
__decorate([
|
|
299
|
+
(0, __1.synchronized)(true, 'refreshAuthToken')
|
|
300
|
+
], HevyAPIClient.prototype, "forceRefreshAuthToken", null);
|
|
301
|
+
__decorate([
|
|
302
|
+
(0, __1.synchronized)(true, 'refreshAuthToken')
|
|
303
|
+
], HevyAPIClient.prototype, "waitForTokenRefresh", null);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./APIClient"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface HTTPClient {
|
|
2
|
+
get<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
3
|
+
delete<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
4
|
+
head<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
5
|
+
options<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
6
|
+
post<T, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
7
|
+
put<T, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
8
|
+
patch<T, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
|
|
9
|
+
}
|
|
10
|
+
export interface RequestConfig {
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
baseURL?: string;
|
|
13
|
+
params?: unknown;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}
|
|
16
|
+
export type HTTPMethod = keyof {
|
|
17
|
+
[K in keyof HTTPClient as HTTPClient[K] extends (...args: any) => Promise<HTTPResponse<never>> ? K : never]: never;
|
|
18
|
+
};
|
|
19
|
+
export interface HTTPRequestFactory<T = unknown> {
|
|
20
|
+
method: HTTPMethod;
|
|
21
|
+
url: string;
|
|
22
|
+
try(): Promise<HTTPResponse<T>>;
|
|
23
|
+
}
|
|
24
|
+
export type HTTPErrorHandler<E, T = unknown, R = unknown> = (response: HTTPResponse<T>, request: HTTPRequestFactory<R>, extraData: E) => void;
|
|
25
|
+
export interface HTTPResponse<T = unknown> {
|
|
26
|
+
status: number;
|
|
27
|
+
data: T;
|
|
28
|
+
}
|
|
29
|
+
export interface HTTPError<T = unknown> extends Error {
|
|
30
|
+
response: HTTPResponse<T>;
|
|
31
|
+
}
|
|
32
|
+
export declare const isHTTPError: <T = unknown>(e: unknown) => e is HTTPError<T>;
|
|
33
|
+
export declare const isHTTPErrorResponse: (response: HTTPResponse<unknown>) => response is HTTPResponse<{
|
|
34
|
+
error: string;
|
|
35
|
+
}>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isHTTPErrorResponse = exports.isHTTPError = void 0;
|
|
4
|
+
const isHTTPError = (e) => {
|
|
5
|
+
return (e instanceof Error &&
|
|
6
|
+
'response' in e &&
|
|
7
|
+
typeof e.response === 'object' &&
|
|
8
|
+
e.response !== null &&
|
|
9
|
+
'status' in e.response &&
|
|
10
|
+
typeof e.response.status === 'number');
|
|
11
|
+
};
|
|
12
|
+
exports.isHTTPError = isHTTPError;
|
|
13
|
+
const isHTTPErrorResponse = (response) => response.status >= 400 &&
|
|
14
|
+
typeof response.data === 'object' &&
|
|
15
|
+
response.data !== null &&
|
|
16
|
+
'error' in response.data &&
|
|
17
|
+
typeof response.data.error === 'string';
|
|
18
|
+
exports.isHTTPErrorResponse = isHTTPErrorResponse;
|
package/built/async.d.ts
CHANGED
|
@@ -34,3 +34,14 @@ export declare const allToResolveOrReject: <T>(promises: Promise<T>[]) => Promis
|
|
|
34
34
|
* await handleRejection(fetch('https://hevy.com/blabla'));
|
|
35
35
|
*/
|
|
36
36
|
export declare const handleRejection: <T>(p: Promise<T>) => Promise<T>;
|
|
37
|
+
type MethodDecorator<T> = (target: unknown, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<(...args: any[]) => T>) => void;
|
|
38
|
+
export declare function synchronized(queue: boolean): MethodDecorator<Promise<any>>;
|
|
39
|
+
export declare function synchronized(queue: boolean, id: any): MethodDecorator<Promise<any>>;
|
|
40
|
+
export declare function synchronized(target: unknown, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<any>>): void;
|
|
41
|
+
/** @deprecated Using this on a synchronous function has no effect */
|
|
42
|
+
export declare function synchronized(queue: boolean): MethodDecorator<any>;
|
|
43
|
+
/** @deprecated Using this on a synchronous function has no effect */
|
|
44
|
+
export declare function synchronized(queue: boolean, id: any): MethodDecorator<any>;
|
|
45
|
+
/** @deprecated Using this on a synchronous function has no effect */
|
|
46
|
+
export declare function synchronized(target: unknown, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<(...args: any[]) => any>): void;
|
|
47
|
+
export {};
|
package/built/async.js
CHANGED
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.handleRejection = exports.allToResolveOrReject = void 0;
|
|
13
|
+
exports.synchronized = synchronized;
|
|
13
14
|
/**
|
|
14
15
|
* The difference between this and `Promise.all` is that `Promise.all` rejects
|
|
15
16
|
* when any promise rejects, and this resolves when all promises resolve _or_
|
|
@@ -58,3 +59,50 @@ exports.allToResolveOrReject = allToResolveOrReject;
|
|
|
58
59
|
*/
|
|
59
60
|
const handleRejection = (p) => (p.catch(() => { }), p);
|
|
60
61
|
exports.handleRejection = handleRejection;
|
|
62
|
+
function synchronized(arg0, arg1, arg2) {
|
|
63
|
+
if (arguments.length === 1) {
|
|
64
|
+
return function (target, propertyKey, descriptor) {
|
|
65
|
+
return synchronizedDecoratorFactory(target, propertyKey, descriptor, arg0);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
else if (arguments.length === 2) {
|
|
69
|
+
return function (target, propertyKey, descriptor) {
|
|
70
|
+
return synchronizedDecoratorFactory(target, propertyKey, descriptor, arg0, arg1);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return synchronizedDecoratorFactory(arg0, arg1, arg2, true);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const $queue = Symbol();
|
|
78
|
+
function synchronizedDecoratorFactory(_target, _propertyKey, descriptor, wait, id) {
|
|
79
|
+
const origFn = descriptor.value;
|
|
80
|
+
const lockId = arguments.length === 5 ? id : Symbol();
|
|
81
|
+
descriptor.value = function (...args) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
var _c;
|
|
85
|
+
const queue = ((_b = (_c = ((_a = this[$queue]) !== null && _a !== void 0 ? _a : (this[$queue] = {})))[lockId]) !== null && _b !== void 0 ? _b : (_c[lockId] = []));
|
|
86
|
+
if (queue.length > 0 && !wait) {
|
|
87
|
+
throw new Error('Operation is already in progress');
|
|
88
|
+
}
|
|
89
|
+
let done;
|
|
90
|
+
const thisCall = new Promise((resolve) => {
|
|
91
|
+
done = resolve;
|
|
92
|
+
});
|
|
93
|
+
const waiting = [...queue];
|
|
94
|
+
queue.push(thisCall);
|
|
95
|
+
yield (0, exports.allToResolveOrReject)(waiting);
|
|
96
|
+
try {
|
|
97
|
+
return yield origFn.apply(this, args);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
done();
|
|
101
|
+
if (queue.shift() !== thisCall) {
|
|
102
|
+
// eslint-disable-next-line no-unsafe-finally
|
|
103
|
+
throw new Error('Assertion failed: @synchronized queue is mangled');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
}
|
package/built/index.d.ts
CHANGED
|
@@ -217,6 +217,19 @@ export interface SocialLoginResult {
|
|
|
217
217
|
email: string;
|
|
218
218
|
is_new_user: boolean;
|
|
219
219
|
}
|
|
220
|
+
export interface ClientAuthToken {
|
|
221
|
+
access_token: string;
|
|
222
|
+
refresh_token: string;
|
|
223
|
+
expires_at: Date;
|
|
224
|
+
}
|
|
225
|
+
export interface ClientRefreshTokenRequest {
|
|
226
|
+
refresh_token: string;
|
|
227
|
+
}
|
|
228
|
+
export interface ClientAuthTokenResponse {
|
|
229
|
+
access_token: string;
|
|
230
|
+
refresh_token: string;
|
|
231
|
+
expires_at: string;
|
|
232
|
+
}
|
|
220
233
|
export interface UsernameAvailabilityResponse {
|
|
221
234
|
isAvailable: boolean;
|
|
222
235
|
suggestions: string[];
|
|
@@ -919,7 +932,6 @@ export interface PostWorkoutRequestWorkout {
|
|
|
919
932
|
biometrics?: WorkoutBiometrics;
|
|
920
933
|
is_biometrics_public: boolean;
|
|
921
934
|
is_trainer_workout: boolean;
|
|
922
|
-
trainer_program_id?: string;
|
|
923
935
|
}
|
|
924
936
|
export declare const isHeartRateSamples: (x: any) => x is HeartRateSample[];
|
|
925
937
|
export declare const isWorkoutBiometrics: (x: any) => x is WorkoutBiometrics;
|
|
@@ -1209,7 +1221,6 @@ export interface OutstandingInvitesForCoachTeamResponse {
|
|
|
1209
1221
|
invites: CoachTeamInvite[];
|
|
1210
1222
|
}
|
|
1211
1223
|
export interface HevyTrainerProgram {
|
|
1212
|
-
id: string;
|
|
1213
1224
|
created_at: string;
|
|
1214
1225
|
updated_at: string;
|
|
1215
1226
|
title: string;
|
package/built/typeUtils.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type Lookup<T extends readonly unknown[]> = T[number];
|
|
2
2
|
export declare const isInArray: <T, ReadonlyArrayOfT extends readonly T[]>(value: T, array: ReadonlyArrayOfT) => value is Lookup<ReadonlyArrayOfT>;
|
|
3
|
+
export type DeepReadonly<T> = {
|
|
4
|
+
readonly [P in keyof T]: T[P] extends (...args: any[]) => any ? T[P] : DeepReadonly<T[P]>;
|
|
5
|
+
};
|
|
3
6
|
export declare const exhaustiveTypeCheck: (_: never) => undefined;
|
|
4
7
|
export declare const exhaustiveTypeException: (type: never) => Error;
|
|
5
8
|
export type Some<T> = {
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hevy-shared",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.809",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "built/index.js",
|
|
6
6
|
"types": "built/index.d.ts",
|
|
7
7
|
"prepublish": "tsc",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"prepare": "tsc --skipLibCheck",
|
|
10
|
-
"build": "tsc",
|
|
10
|
+
"build": "tsc --removeComments && tsc --emitDeclarationOnly",
|
|
11
11
|
"types": "tsc --skipLibCheck",
|
|
12
12
|
"p": "npm run test && npm version patch && npm run build && npm publish && git push",
|
|
13
13
|
"ci-build": "./scripts/publish.sh",
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
"lint-fix": "eslint . --ext .ts --fix",
|
|
16
16
|
"test": "jest"
|
|
17
17
|
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./built/index.js",
|
|
20
|
+
"./API": "./built/API/index.js"
|
|
21
|
+
},
|
|
22
|
+
"typesVersions": {
|
|
23
|
+
"*": {
|
|
24
|
+
"API": [
|
|
25
|
+
"built/API/index.d.ts"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
18
29
|
"files": [
|
|
19
30
|
"built"
|
|
20
31
|
],
|