hevy-shared 1.0.962 → 1.0.964

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.
Files changed (86) hide show
  1. package/README.md +17 -2
  2. package/built/API/APIClient.d.ts +157 -0
  3. package/built/API/APIClient.js +381 -0
  4. package/built/API/index.d.ts +2 -0
  5. package/built/API/index.js +18 -0
  6. package/built/API/types.d.ts +38 -0
  7. package/built/API/types.js +18 -0
  8. package/built/adjustEventTokens.d.ts +16 -0
  9. package/built/adjustEventTokens.js +18 -0
  10. package/built/adminPermissions.d.ts +4 -0
  11. package/built/adminPermissions.js +22 -0
  12. package/built/async.d.ts +50 -0
  13. package/built/async.js +170 -0
  14. package/built/chat.d.ts +25 -23
  15. package/built/coachPlans.d.ts +2 -1
  16. package/built/coachPlans.js +2 -2
  17. package/built/cue.d.ts +12 -0
  18. package/built/cue.js +22 -0
  19. package/built/exerciseLocaleUtils.d.ts +17 -0
  20. package/built/exerciseLocaleUtils.js +62 -0
  21. package/built/filterExercises.d.ts +19 -3
  22. package/built/filterExercises.js +72 -60
  23. package/built/hevyTrainer.d.ts +250 -0
  24. package/built/hevyTrainer.js +676 -0
  25. package/built/index.d.ts +1217 -304
  26. package/built/index.js +268 -75
  27. package/built/muscleHeatmaps.d.ts +31 -0
  28. package/built/muscleHeatmaps.js +68 -0
  29. package/built/muscleSplits.d.ts +36 -0
  30. package/built/muscleSplits.js +100 -0
  31. package/built/normalizedWorkoutUtils.d.ts +88 -0
  32. package/built/normalizedWorkoutUtils.js +112 -0
  33. package/built/notifications.d.ts +215 -0
  34. package/built/notifications.js +9 -0
  35. package/built/routineUtils.d.ts +14 -0
  36. package/built/routineUtils.js +186 -0
  37. package/built/setIndicatorUtils.d.ts +4 -3
  38. package/built/setIndicatorUtils.js +15 -1
  39. package/built/tests/async.test.d.ts +1 -0
  40. package/built/tests/async.test.js +49 -0
  41. package/built/tests/hevyTrainer.test.d.ts +1 -0
  42. package/built/tests/hevyTrainer.test.js +1199 -0
  43. package/built/tests/muscleSplit.test.d.ts +1 -0
  44. package/built/tests/muscleSplit.test.js +153 -0
  45. package/built/tests/routineUtils.test.d.ts +1 -0
  46. package/built/tests/routineUtils.test.js +745 -0
  47. package/built/tests/testUtils.d.ts +85 -0
  48. package/built/tests/testUtils.js +319 -0
  49. package/built/tests/utils.test.js +748 -0
  50. package/built/tests/workoutVolume.test.js +165 -49
  51. package/built/translations/index.d.ts +2 -0
  52. package/built/translations/index.js +18 -0
  53. package/built/translations/translationUtils.d.ts +2 -0
  54. package/built/translations/translationUtils.js +61 -0
  55. package/built/translations/types.d.ts +8 -0
  56. package/built/translations/types.js +20 -0
  57. package/built/typeUtils.d.ts +70 -0
  58. package/built/typeUtils.js +55 -0
  59. package/built/units.d.ts +14 -7
  60. package/built/units.js +24 -14
  61. package/built/utils.d.ts +192 -5
  62. package/built/utils.js +598 -85
  63. package/built/websocket.d.ts +14 -2
  64. package/built/workoutVolume.d.ts +24 -5
  65. package/built/workoutVolume.js +25 -34
  66. package/package.json +30 -9
  67. package/.eslintignore +0 -2
  68. package/.eslintrc +0 -21
  69. package/.github/workflows/ci.yml +0 -15
  70. package/.github/workflows/npm-publish.yml +0 -59
  71. package/.github/workflows/pr-auto-assign.yml +0 -15
  72. package/.prettierrc.js +0 -5
  73. package/jest.config.js +0 -4
  74. package/src/chat.ts +0 -130
  75. package/src/coachPlans.ts +0 -57
  76. package/src/constants.ts +0 -14
  77. package/src/filterExercises.ts +0 -222
  78. package/src/index.ts +0 -1576
  79. package/src/setIndicatorUtils.ts +0 -137
  80. package/src/tests/utils.test.ts +0 -156
  81. package/src/tests/workoutVolume.test.ts +0 -93
  82. package/src/units.ts +0 -41
  83. package/src/utils.ts +0 -516
  84. package/src/websocket.ts +0 -36
  85. package/src/workoutVolume.ts +0 -175
  86. package/tsconfig.json +0 -70
package/README.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # hevy-shared
2
2
  Common code shared by the other repos.
3
3
 
4
- ## Deploying
5
- Merging PRs to `master` will automatically trigger the deployment of a new patch version to npm using github actions (see ./github/workflows). Go to https://www.npmjs.com/package/hevy-shared for more information
4
+ ## Publishing to npm
5
+
6
+ There are two ways of publishing a new version of `hevy-shared` to npm.
7
+
8
+ ### Automatically
9
+
10
+ Merging PRs to `master` will automatically trigger the publishing of a new patch version to npm using Github Actions (see .github/workflows). Go to https://www.npmjs.com/package/hevy-shared for more information.
11
+
12
+ **Important note:** Please do **NOT** merge your `hevy-shared` PRs into `master` until _all_ the other repos (regardless of whether they use npm or Git submodules) which depend on your `hevy-shared` PR have their respective PRs merged into their respective main branches.
13
+ Doing so should help avoid type errors and conflicts more than doing it the other way around, even though they can still happen.
14
+
15
+ ### Manually
16
+
17
+ To publish manually, run `npm run ci-build` _on the branch of your choice_. This is useful if you need to get your changes into a repo which doesn't use Git submodules.
18
+
19
+ Running the command will print the next unused version number, which should be the version of the newly published package. You will just have to wait until it's actually published before you can install it.
20
+ You can run `npm view hevy-shared version` to check the current `latest` version that's available on npm at any given moment.
@@ -0,0 +1,157 @@
1
+ import { ClientAuthToken, DeepReadonly } from '..';
2
+ import { RequestConfig, HTTPResponse, HTTPClient, HTTPRequestCompletionCallback, HTTPErrorHandler } from './types';
3
+ interface HevyAPIClientConfig<UserContext> {
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, userContext: UserContext): void;
59
+ /**
60
+ * Defines an arbitrary object to be passed back to the `onNewAuthToken`. The
61
+ * value is computed at the time when the request is made. Useful for passing
62
+ * back state that may have gotten changed during the time while the request
63
+ * was being processed, such as holding onto a userId across a logout action.
64
+ *
65
+ * Optional.
66
+ */
67
+ getUserContext?(): UserContext;
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 has expired.
71
+ *
72
+ * Optional.
73
+ *
74
+ * @default ...
75
+ */
76
+ isAccessTokenExpiredResponse?(response: HTTPResponse<unknown>): boolean;
77
+ /**
78
+ * Callback to use when receiving an HTTP error response from the backend, to
79
+ * determine whether it is a response indicating that the token is invalid.
80
+ *
81
+ * Optional.
82
+ *
83
+ * @default ...
84
+ */
85
+ isAccessTokenInvalidResponse?(response: HTTPResponse<unknown>): boolean;
86
+ }
87
+ export declare class HevyAPIClient<UserContext = never> {
88
+ private static readonly DEFAULT_TOKEN_EXPIRY_SAFETY_THRESHOLD_MS;
89
+ private static readonly DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS;
90
+ private static readonly DEFAULT_TOKEN_REFRESH_THROTTLE_MS;
91
+ private static readonly DEFAULT_REFRESH_AUTH_TOKEN_API_ENDPOINT;
92
+ private readonly _config;
93
+ private _requestCompletionCallbacks;
94
+ private _errorHandlers;
95
+ private _authTokenFactory;
96
+ private _legacyAuthToken;
97
+ private _httpClient;
98
+ private _lastTokenRefresh;
99
+ private _lastSessionDelete;
100
+ constructor(httpClient: HTTPClient, config: HevyAPIClientConfig<UserContext>);
101
+ private get _authToken();
102
+ private get _authHeaders();
103
+ private _addHeaders;
104
+ private refreshExpiredAuthToken;
105
+ private forceRefreshAuthToken;
106
+ private waitForTokenRefresh;
107
+ private get _isTokenRecentlyRefreshed();
108
+ private _refreshAuthToken;
109
+ private _handleResponse;
110
+ isAccessTokenExpiredResponse(response: HTTPResponse<unknown>): boolean;
111
+ isAccessTokenInvalidResponse(response: HTTPResponse<unknown>): boolean;
112
+ get refreshAuthTokenApiEndpoint(): {
113
+ readonly method: "post";
114
+ readonly url: string;
115
+ };
116
+ setAuthToken(newAuthToken: {
117
+ authToken: DeepReadonly<ClientAuthToken> | null;
118
+ legacyAuthToken: string | null;
119
+ }): Promise<void>;
120
+ setAuthToken(newAuthToken: {
121
+ getAuthToken: () => ClientAuthToken | null;
122
+ legacyAuthToken: string | null;
123
+ }): Promise<void>;
124
+ clearAuthToken(): Promise<void>;
125
+ get isAuthenticated(): boolean;
126
+ markSessionDeleted(): void;
127
+ /**
128
+ * Adds a callback to be executed whenever a request has finished processing.
129
+ * This means either that the request has received a response, or that there
130
+ * was an error. In the case of an error, it may be either an HTTP error from
131
+ * the server, or some other type of error, such as a network error.
132
+ *
133
+ * This is a lower level API than {@link attachErrorHandler} - prefer using
134
+ * that one instead of this one if it's enough to suit your needs.
135
+ */
136
+ attachRequestCompletionCallback(onResult: HTTPRequestCompletionCallback): void;
137
+ removeRequestCompletionCallbacks(): void;
138
+ /**
139
+ * Adds a callback to be executed on receiving an HTTP error from the server.
140
+ * This callback will not be executed for any other type of error, such as a
141
+ * network error. For that and more, use {@link attachRequestCompletionCallback}.
142
+ */
143
+ attachErrorHandler(onError: HTTPErrorHandler<{
144
+ willRetry: boolean;
145
+ isTokenRefreshedAfterRequest: boolean;
146
+ isPreviousSession: boolean;
147
+ }>): void;
148
+ removeErrorHandlers(): void;
149
+ get<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
150
+ delete<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
151
+ head<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
152
+ options<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
153
+ post<T = never, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
154
+ put<T = never, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
155
+ patch<T = never, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
156
+ }
157
+ export {};
@@ -0,0 +1,381 @@
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._requestCompletionCallbacks = [];
24
+ this._errorHandlers = [];
25
+ this._authTokenFactory = {
26
+ type: 'value',
27
+ value: null,
28
+ };
29
+ this._legacyAuthToken = null;
30
+ this._addHeaders = (headers, config) => (Object.assign(Object.assign({}, config), { headers: Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.headers), headers) }));
31
+ this._httpClient = httpClient;
32
+ 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 &&
33
+ (0, types_1.isHTTPErrorResponse)(response) &&
34
+ response.data.error === 'AccessTokenExpired', isAccessTokenInvalidResponse: (response) => response.status === 401 &&
35
+ (0, types_1.isHTTPErrorResponse)(response) &&
36
+ response.data.error === 'InvalidAccessToken', getUserContext: () => undefined }, config);
37
+ const classProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter((m) => m in this && typeof m === 'string' && m !== 'constructor');
38
+ classProperties.forEach((m) => {
39
+ if (typeof this[m] === 'function')
40
+ this[m] = this[m].bind(this);
41
+ });
42
+ }
43
+ get _authToken() {
44
+ switch (this._authTokenFactory.type) {
45
+ case 'value':
46
+ return this._authTokenFactory.value;
47
+ case 'getter':
48
+ return this._authTokenFactory.get();
49
+ }
50
+ }
51
+ get _authHeaders() {
52
+ return Object.assign(Object.assign(Object.assign({}, (this._authToken
53
+ ? { authorization: `Bearer ${this._authToken.access_token}` }
54
+ : {})), (this._legacyAuthToken ? { 'auth-token': this._legacyAuthToken } : {})), { 'x-client-time': String(new Date().valueOf() / 1000) });
55
+ }
56
+ refreshExpiredAuthToken(userContext) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const { _authToken } = this;
59
+ if (!_authToken)
60
+ return;
61
+ if (new Date().valueOf() <=
62
+ new Date(_authToken.expires_at).valueOf() -
63
+ this._config.tokenExpirySafetyThresholdMs) {
64
+ // Token is still valid, at least according to the client. Don't refresh.
65
+ return;
66
+ }
67
+ yield this._refreshAuthToken(_authToken, userContext);
68
+ });
69
+ }
70
+ forceRefreshAuthToken(userContext) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ const { _authToken } = this;
73
+ if (!_authToken)
74
+ return;
75
+ yield this._refreshAuthToken(_authToken, userContext);
76
+ });
77
+ }
78
+ waitForTokenRefresh() {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ // This may look like it does nothing, but that is absolutely not the case.
81
+ // Do not remove this function!
82
+ });
83
+ }
84
+ get _isTokenRecentlyRefreshed() {
85
+ const { _lastTokenRefresh } = this;
86
+ return (_lastTokenRefresh !== undefined &&
87
+ _lastTokenRefresh.valueOf() >
88
+ new Date().valueOf() - this._config.tokenRefreshThrottleMs);
89
+ }
90
+ _refreshAuthToken(authToken, userContext) {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ if (this._isTokenRecentlyRefreshed) {
93
+ // We have called this function recently. Do not ever spam this endpoint.
94
+ // Also, if any request needs to be retried due to issues with the client
95
+ // clock being out of sync with the backend, do that with the last token
96
+ // that was obtained after a 401 with the `AccessTokenExpired` response.
97
+ return;
98
+ }
99
+ this._lastTokenRefresh = new Date();
100
+ const { access_token, refresh_token } = authToken;
101
+ const { method, url } = this.refreshAuthTokenApiEndpoint;
102
+ const headers = {
103
+ authorization: `Bearer ${access_token}`,
104
+ 'x-client-time': String(new Date().valueOf() / 1000),
105
+ };
106
+ // This will throw and bail out if the request fails.
107
+ const response = yield this._handleResponse({
108
+ method,
109
+ url,
110
+ headers,
111
+ try: () => this._httpClient[method](url, { refresh_token }, { headers }),
112
+ }, userContext, false);
113
+ const newToken = {
114
+ access_token: response.data.access_token,
115
+ refresh_token: response.data.refresh_token,
116
+ expires_at: new Date(response.data.expires_at),
117
+ };
118
+ /**
119
+ * Do not attempt to auto-refresh the token before this time - by default,
120
+ * {@link HevyAPIClient.DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS 15 mins}
121
+ * after the current time. Normally the backend will generate a token that
122
+ * lives for at least 15 minutes, but since it sends the expiry timestamp
123
+ * rather than a max age in relative time units, even if the client's clock
124
+ * is severely out of sync, we don't want to think that the token has
125
+ * expired when in reality it hasn't.
126
+ *
127
+ * If we ever change the access tokens to really live for less than 15 min,
128
+ * this assertion will no longer hold, and any tokens that are expired
129
+ * despite the client not realising it will be handled by the logic that
130
+ * handles 401s with the `AccessTokenExpired` error response.
131
+ */
132
+ const earliestAutoRefreshUnixMs = new Date().valueOf() + this._config.accessTokenMinimumValidAgeMs;
133
+ if (newToken.expires_at.valueOf() < earliestAutoRefreshUnixMs) {
134
+ newToken.expires_at = new Date(earliestAutoRefreshUnixMs);
135
+ }
136
+ if (this._authTokenFactory.type === 'value') {
137
+ this._authTokenFactory.value = newToken;
138
+ }
139
+ this._config.onNewAuthToken(newToken, userContext);
140
+ });
141
+ }
142
+ _handleResponse(request_1, userContext_1) {
143
+ return __awaiter(this, arguments, void 0, function* (request, userContext, retryOnFailure = !this._isTokenRecentlyRefreshed) {
144
+ const requestTime = new Date();
145
+ try {
146
+ const response = yield request.try();
147
+ if (response.status >= 400) {
148
+ throw Object.assign(new Error(`Request failed with status code ${response.status}`), {
149
+ response,
150
+ });
151
+ }
152
+ this._requestCompletionCallbacks.forEach((cb) => cb({ isSuccess: true, value: response }, request));
153
+ return response;
154
+ }
155
+ catch (e) {
156
+ this._requestCompletionCallbacks.forEach((cb) => cb({ isSuccess: false, error: e }, request));
157
+ if (!(0, types_1.isHTTPError)(e))
158
+ throw e;
159
+ const { response } = e;
160
+ /**
161
+ * We've had a {@link _refreshAuthToken} called _after_ this request was
162
+ * made. If that request reached the server before this one did, this one
163
+ * will have failed with a 401 because it's using the old token.
164
+ *
165
+ * In this (relatively unlikely) scenario, we will want to retry the
166
+ * request, and also we will not want to log the user out.
167
+ */
168
+ const isTokenRefreshedAfterRequest = this._lastTokenRefresh !== undefined &&
169
+ requestTime < this._lastTokenRefresh;
170
+ /**
171
+ * Similar to the above, but this will be `true` if the user has logged
172
+ * out after the request was made, i.e. the session that the request was
173
+ * made within is is (probably) no longer valid. In this case, we don't
174
+ * want to retry the request, but we also don't want to log the user out,
175
+ * in case that they've logged in to another session in the meantime.
176
+ */
177
+ const isPreviousSession = this._lastSessionDelete !== undefined &&
178
+ requestTime < this._lastSessionDelete;
179
+ this._errorHandlers.forEach((cb) => cb(response, request, {
180
+ willRetry: retryOnFailure,
181
+ isTokenRefreshedAfterRequest,
182
+ isPreviousSession,
183
+ }));
184
+ if (retryOnFailure && this.isAccessTokenExpiredResponse(response)) {
185
+ yield this.forceRefreshAuthToken(userContext);
186
+ return yield this._handleResponse(request, userContext, false);
187
+ }
188
+ else if (retryOnFailure &&
189
+ this.isAccessTokenInvalidResponse(response) &&
190
+ isTokenRefreshedAfterRequest &&
191
+ !isPreviousSession) {
192
+ yield this.waitForTokenRefresh();
193
+ return yield this._handleResponse(request, userContext, false);
194
+ }
195
+ else {
196
+ throw e;
197
+ }
198
+ }
199
+ });
200
+ }
201
+ isAccessTokenExpiredResponse(response) {
202
+ return this._config.isAccessTokenExpiredResponse(response);
203
+ }
204
+ isAccessTokenInvalidResponse(response) {
205
+ return this._config.isAccessTokenInvalidResponse(response);
206
+ }
207
+ get refreshAuthTokenApiEndpoint() {
208
+ return Object.assign({}, this._config.refreshAuthTokenApiEndpoint);
209
+ }
210
+ setAuthToken(newAuthToken) {
211
+ return __awaiter(this, void 0, void 0, function* () {
212
+ yield this.waitForTokenRefresh();
213
+ const { authToken, getAuthToken, legacyAuthToken } = newAuthToken;
214
+ if (getAuthToken !== undefined && authToken === undefined) {
215
+ this._authTokenFactory = { type: 'getter', get: getAuthToken };
216
+ }
217
+ else if (authToken !== undefined && getAuthToken === undefined) {
218
+ this._authTokenFactory = { type: 'value', value: authToken };
219
+ }
220
+ else {
221
+ throw new Error("Invalid arguments: exactly one of `authToken' or `getAuthToken' is required");
222
+ }
223
+ this._legacyAuthToken = legacyAuthToken;
224
+ this._lastTokenRefresh = undefined;
225
+ });
226
+ }
227
+ clearAuthToken() {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ yield this.waitForTokenRefresh();
230
+ this._authTokenFactory = { type: 'value', value: null };
231
+ this._legacyAuthToken = null;
232
+ this._lastTokenRefresh = undefined;
233
+ });
234
+ }
235
+ get isAuthenticated() {
236
+ if (this._authTokenFactory.type === 'value' &&
237
+ this._authTokenFactory.value === null) {
238
+ return false;
239
+ }
240
+ else {
241
+ return true;
242
+ }
243
+ }
244
+ markSessionDeleted() {
245
+ this._lastSessionDelete = new Date();
246
+ }
247
+ /**
248
+ * Adds a callback to be executed whenever a request has finished processing.
249
+ * This means either that the request has received a response, or that there
250
+ * was an error. In the case of an error, it may be either an HTTP error from
251
+ * the server, or some other type of error, such as a network error.
252
+ *
253
+ * This is a lower level API than {@link attachErrorHandler} - prefer using
254
+ * that one instead of this one if it's enough to suit your needs.
255
+ */
256
+ attachRequestCompletionCallback(onResult) {
257
+ this._requestCompletionCallbacks.push(onResult);
258
+ }
259
+ removeRequestCompletionCallbacks() {
260
+ this._requestCompletionCallbacks.length = 0;
261
+ }
262
+ /**
263
+ * Adds a callback to be executed on receiving an HTTP error from the server.
264
+ * This callback will not be executed for any other type of error, such as a
265
+ * network error. For that and more, use {@link attachRequestCompletionCallback}.
266
+ */
267
+ attachErrorHandler(onError) {
268
+ this._errorHandlers.push(onError);
269
+ }
270
+ removeErrorHandlers() {
271
+ this._errorHandlers.length = 0;
272
+ }
273
+ get(url, config) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ const userContext = this._config.getUserContext();
276
+ yield this.refreshExpiredAuthToken(userContext);
277
+ const method = 'get';
278
+ return this._handleResponse({
279
+ method,
280
+ url,
281
+ headers: this._authHeaders,
282
+ try: () => this._httpClient[method](url, this._addHeaders(this._authHeaders, config)),
283
+ }, userContext);
284
+ });
285
+ }
286
+ delete(url, config) {
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ const userContext = this._config.getUserContext();
289
+ yield this.refreshExpiredAuthToken(userContext);
290
+ const method = 'delete';
291
+ return this._handleResponse({
292
+ method,
293
+ url,
294
+ headers: this._authHeaders,
295
+ try: () => this._httpClient[method](url, this._addHeaders(this._authHeaders, config)),
296
+ }, userContext);
297
+ });
298
+ }
299
+ head(url, config) {
300
+ return __awaiter(this, void 0, void 0, function* () {
301
+ const userContext = this._config.getUserContext();
302
+ yield this.refreshExpiredAuthToken(userContext);
303
+ const method = 'head';
304
+ return this._handleResponse({
305
+ method,
306
+ url,
307
+ headers: this._authHeaders,
308
+ try: () => this._httpClient[method](url, this._addHeaders(this._authHeaders, config)),
309
+ }, userContext);
310
+ });
311
+ }
312
+ options(url, config) {
313
+ return __awaiter(this, void 0, void 0, function* () {
314
+ const userContext = this._config.getUserContext();
315
+ yield this.refreshExpiredAuthToken(userContext);
316
+ const method = 'options';
317
+ return this._handleResponse({
318
+ method,
319
+ url,
320
+ headers: this._authHeaders,
321
+ try: () => this._httpClient[method](url, this._addHeaders(this._authHeaders, config)),
322
+ }, userContext);
323
+ });
324
+ }
325
+ post(url, data, config) {
326
+ return __awaiter(this, void 0, void 0, function* () {
327
+ const userContext = this._config.getUserContext();
328
+ yield this.refreshExpiredAuthToken(userContext);
329
+ const method = 'post';
330
+ return this._handleResponse({
331
+ method,
332
+ url,
333
+ headers: this._authHeaders,
334
+ try: () => this._httpClient[method](url, data, this._addHeaders(this._authHeaders, config)),
335
+ }, userContext);
336
+ });
337
+ }
338
+ put(url, data, config) {
339
+ return __awaiter(this, void 0, void 0, function* () {
340
+ const userContext = this._config.getUserContext();
341
+ yield this.refreshExpiredAuthToken(userContext);
342
+ const method = 'put';
343
+ return this._handleResponse({
344
+ method,
345
+ url,
346
+ headers: this._authHeaders,
347
+ try: () => this._httpClient[method](url, data, this._addHeaders(this._authHeaders, config)),
348
+ }, userContext);
349
+ });
350
+ }
351
+ patch(url, data, config) {
352
+ return __awaiter(this, void 0, void 0, function* () {
353
+ const userContext = this._config.getUserContext();
354
+ yield this.refreshExpiredAuthToken(userContext);
355
+ const method = 'patch';
356
+ return this._handleResponse({
357
+ method,
358
+ url,
359
+ headers: this._authHeaders,
360
+ try: () => this._httpClient[method](url, data, this._addHeaders(this._authHeaders, config)),
361
+ }, userContext);
362
+ });
363
+ }
364
+ }
365
+ exports.HevyAPIClient = HevyAPIClient;
366
+ HevyAPIClient.DEFAULT_TOKEN_EXPIRY_SAFETY_THRESHOLD_MS = 60000;
367
+ HevyAPIClient.DEFAULT_ACCESS_TOKEN_MINIMUM_VALID_AGE_MS = 900000;
368
+ HevyAPIClient.DEFAULT_TOKEN_REFRESH_THROTTLE_MS = 20000;
369
+ HevyAPIClient.DEFAULT_REFRESH_AUTH_TOKEN_API_ENDPOINT = {
370
+ method: 'post',
371
+ url: 'auth/refresh_token',
372
+ };
373
+ __decorate([
374
+ (0, __1.synchronized)(true, 'refreshAuthToken')
375
+ ], HevyAPIClient.prototype, "refreshExpiredAuthToken", null);
376
+ __decorate([
377
+ (0, __1.synchronized)(true, 'refreshAuthToken')
378
+ ], HevyAPIClient.prototype, "forceRefreshAuthToken", null);
379
+ __decorate([
380
+ (0, __1.synchronized)(true, 'refreshAuthToken')
381
+ ], 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,38 @@
1
+ import { Result } from '..';
2
+ export interface HTTPClient {
3
+ get<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
4
+ delete<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
5
+ head<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
6
+ options<T>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
7
+ post<T, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
8
+ put<T, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
9
+ patch<T, R = unknown>(url: string, data?: R, config?: RequestConfig): Promise<HTTPResponse<T>>;
10
+ }
11
+ export interface RequestConfig {
12
+ headers?: Record<string, string>;
13
+ baseURL?: string;
14
+ params?: unknown;
15
+ signal?: AbortSignal;
16
+ }
17
+ export type HTTPMethod = keyof {
18
+ [K in keyof HTTPClient as HTTPClient[K] extends (...args: any) => Promise<HTTPResponse<never>> ? K : never]: never;
19
+ };
20
+ export interface HTTPRequestFactory<T = unknown> {
21
+ method: HTTPMethod;
22
+ url: string;
23
+ headers: Record<string, string>;
24
+ try(): Promise<HTTPResponse<T>>;
25
+ }
26
+ export type HTTPRequestCompletionCallback<T = unknown, R = unknown> = (result: Result<HTTPResponse<T>>, request: HTTPRequestFactory<R>) => void;
27
+ export type HTTPErrorHandler<E, T = unknown, R = unknown> = (response: HTTPResponse<T>, request: HTTPRequestFactory<R>, extraData: E) => void;
28
+ export interface HTTPResponse<T = unknown> {
29
+ status: number;
30
+ data: T;
31
+ }
32
+ export interface HTTPError<T = unknown> extends Error {
33
+ response: HTTPResponse<T>;
34
+ }
35
+ export declare const isHTTPError: <T = unknown>(e: unknown) => e is HTTPError<T>;
36
+ export declare const isHTTPErrorResponse: (response: HTTPResponse<unknown>) => response is HTTPResponse<{
37
+ error: string;
38
+ }>;
@@ -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;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Manage adjust events at https://suite.adjust.com/apps/na3q7q6avq4g/events
3
+ */
4
+ export declare const ADJUST_EVENT_TOKENS: {
5
+ logWorkout: string;
6
+ purchase: string;
7
+ signupComplete: string;
8
+ renewal: string;
9
+ updateSexMale: string;
10
+ updateSexFemale: string;
11
+ updateSexOther: string;
12
+ updateAge18Through28: string;
13
+ updateAge29Through44: string;
14
+ updateAge45AndUp: string;
15
+ };
16
+ export type AdjustEventToken = typeof ADJUST_EVENT_TOKENS[keyof typeof ADJUST_EVENT_TOKENS];