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.
@@ -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,2 @@
1
+ export * from './APIClient';
2
+ export * from './types';
@@ -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;
@@ -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.807",
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
  ],