hevy-shared 1.0.875 → 1.0.877

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.
@@ -95,6 +95,7 @@ export declare class HevyAPIClient<UserContext = never> {
95
95
  private _legacyAuthToken;
96
96
  private _httpClient;
97
97
  private _lastTokenRefresh;
98
+ private _lastSessionDelete;
98
99
  constructor(httpClient: HTTPClient, config: HevyAPIClientConfig<UserContext>);
99
100
  private get _authToken();
100
101
  private get _authHeaders();
@@ -121,9 +122,11 @@ export declare class HevyAPIClient<UserContext = never> {
121
122
  }): Promise<void>;
122
123
  clearAuthToken(): Promise<void>;
123
124
  get isAuthenticated(): boolean;
125
+ markSessionDeleted(): void;
124
126
  attachErrorHandler(onError: HTTPErrorHandler<{
125
127
  willRetry: boolean;
126
128
  isTokenRefreshedAfterRequest: boolean;
129
+ isPreviousSession: boolean;
127
130
  }>): void;
128
131
  removeErrorHandlers(): void;
129
132
  get<T = never>(url: string, config?: RequestConfig): Promise<HTTPResponse<T>>;
@@ -161,9 +161,19 @@ class HevyAPIClient {
161
161
  */
162
162
  const isTokenRefreshedAfterRequest = this._lastTokenRefresh !== undefined &&
163
163
  requestTime < this._lastTokenRefresh;
164
+ /**
165
+ * Similar to the above, but this will be `true` if the user has logged
166
+ * out after the request was made, i.e. the session that the request was
167
+ * made within is is (probably) no longer valid. In this case, we don't
168
+ * want to retry the request, but we also don't want to log the user out,
169
+ * in case that they've logged in to another session in the meantime.
170
+ */
171
+ const isPreviousSession = this._lastSessionDelete !== undefined &&
172
+ requestTime < this._lastSessionDelete;
164
173
  this._errorHandlers.forEach((cb) => cb(response, request, {
165
174
  willRetry: retryOnFailure,
166
175
  isTokenRefreshedAfterRequest,
176
+ isPreviousSession,
167
177
  }));
168
178
  if (retryOnFailure && this.isAccessTokenExpiredResponse(response)) {
169
179
  yield this.forceRefreshAuthToken(userContext);
@@ -171,7 +181,8 @@ class HevyAPIClient {
171
181
  }
172
182
  else if (retryOnFailure &&
173
183
  this.isAccessTokenInvalidResponse(response) &&
174
- isTokenRefreshedAfterRequest) {
184
+ isTokenRefreshedAfterRequest &&
185
+ !isPreviousSession) {
175
186
  yield this.waitForTokenRefresh();
176
187
  return yield this._handleResponse(request, userContext, false);
177
188
  }
@@ -224,6 +235,9 @@ class HevyAPIClient {
224
235
  return true;
225
236
  }
226
237
  }
238
+ markSessionDeleted() {
239
+ this._lastSessionDelete = new Date();
240
+ }
227
241
  attachErrorHandler(onError) {
228
242
  this._errorHandlers.push(onError);
229
243
  }
@@ -699,4 +699,76 @@ describe('utils', () => {
699
699
  expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(0);
700
700
  });
701
701
  });
702
+ describe('formatSetValue', () => {
703
+ const baseParams = {
704
+ set: {
705
+ weight_kg: 100,
706
+ reps: 10,
707
+ duration_seconds: 90,
708
+ distance_meters: 1500,
709
+ custom_metric: 15,
710
+ },
711
+ units: {
712
+ weight: 'kg',
713
+ distance: 'kilometers',
714
+ },
715
+ labels: {
716
+ weight: 'kg',
717
+ distance: ' km',
718
+ shortDistance: ' m',
719
+ steps: 'Steps',
720
+ floors: 'Floors',
721
+ },
722
+ };
723
+ it('formats weight and reps for weight-based exercises', () => {
724
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'weight_reps' }))).toBe('100 kg x 10');
725
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'bodyweight_reps' }))).toBe('100 kg x 10');
726
+ });
727
+ it('formats assisted bodyweight reps with negative weight', () => {
728
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'bodyweight_assisted_reps', set: Object.assign(Object.assign({}, baseParams.set), { weight_kg: 15, reps: 8 }) }))).toBe('-15 kg x 8');
729
+ });
730
+ it('formats reps-only exercises', () => {
731
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'reps_only' }))).toBe('10');
732
+ });
733
+ it('formats distance with duration', () => {
734
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'distance_duration' }))).toBe('1.5 km - 1min 30s');
735
+ });
736
+ it('formats duration-only exercises', () => {
737
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'duration' }))).toBe('1min 30s');
738
+ });
739
+ it('formats short distance with weight', () => {
740
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'short_distance_weight', set: Object.assign(Object.assign({}, baseParams.set), { distance_meters: 250 }) }))).toBe('100 kg - 250 m');
741
+ });
742
+ it('handles null reps as zero', () => {
743
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'weight_reps', set: Object.assign(Object.assign({}, baseParams.set), { reps: null }) }))).toBe('100 kg x 0');
744
+ });
745
+ it('formats weight with duration', () => {
746
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'weight_duration' }))).toBe('100 kg - 1min 30s');
747
+ });
748
+ it('formats floors with duration', () => {
749
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'floors_duration' }))).toBe('15 Floors - 1min 30s');
750
+ });
751
+ it('formats steps with duration', () => {
752
+ expect((0, utils_1.formatSetValue)(Object.assign(Object.assign({}, baseParams), { exerciseType: 'steps_duration' }))).toBe('15 Steps - 1min 30s');
753
+ });
754
+ });
755
+ describe('distance', () => {
756
+ it('converts meters to kilometers and rounds to two decimals', () => {
757
+ expect((0, utils_1.distance)(1555, 'kilometers')).toBe(1.56);
758
+ });
759
+ it('converts meters to miles and rounds to two decimals', () => {
760
+ expect((0, utils_1.distance)(1000, 'miles')).toBe(0.62);
761
+ });
762
+ });
763
+ describe('exerciseWeight', () => {
764
+ it('returns rounded kg values for metric units', () => {
765
+ expect((0, utils_1.exerciseWeight)(10.555, 'kg')).toBe(10.56);
766
+ });
767
+ it('converts kg to lbs and rounds to two decimals', () => {
768
+ expect((0, utils_1.exerciseWeight)(100, 'lbs')).toBe(220.46);
769
+ });
770
+ it('rounds lbs values to two decimals', () => {
771
+ expect((0, utils_1.exerciseWeight)(4.535, 'lbs')).toBe(10.0);
772
+ });
773
+ });
702
774
  });
package/built/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Dayjs } from 'dayjs';
2
- import { BaseExerciseTemplate, ExerciseInstructionsStep, ExerciseType, FormatAtText, Result, SetType, StrengthLevel, UserExerciseSet, UserFacingSetIndicator, Weekday } from '.';
2
+ import { BaseExerciseTemplate, DistanceUnit, ExerciseInstructionsStep, ExerciseType, FormatAtText, Result, SetType, StrengthLevel, UserExerciseSet, UserFacingSetIndicator, Weekday, WeightUnit } from '.';
3
3
  /**
4
4
  * Doesn't matter what you throw in the function it'll
5
5
  * always return a number. Non number values will return
@@ -249,5 +249,29 @@ export declare const startOfWeek: (d: Dayjs, firstDayOfWeek: Weekday) => Dayjs;
249
249
  export declare const weekdayNumberMap: {
250
250
  [key in Weekday]: number;
251
251
  };
252
+ export declare const distance: (value: number, distanceUnit: DistanceUnit) => number;
253
+ export declare const exerciseWeight: (value: number, weightUnit: WeightUnit) => number;
254
+ interface getSetValueParams {
255
+ exerciseType: ExerciseType;
256
+ set: {
257
+ weight_kg: number | null;
258
+ reps: number | null;
259
+ duration_seconds: number | null;
260
+ distance_meters: number | null;
261
+ custom_metric: number | null;
262
+ };
263
+ units: {
264
+ weight: WeightUnit;
265
+ distance: DistanceUnit;
266
+ };
267
+ labels: {
268
+ weight: string;
269
+ distance: string;
270
+ shortDistance: string;
271
+ steps: string;
272
+ floors: string;
273
+ };
274
+ }
275
+ export declare const formatSetValue: ({ exerciseType, set, units, labels, }: getSetValueParams) => string;
252
276
  export declare const rawInstructionsToIndexedSteps: (rawInstructions: string) => ExerciseInstructionsStep[];
253
277
  export {};
package/built/utils.js CHANGED
@@ -4,8 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.startOfWeek = exports.calculateCurrentWeekStreak = exports.getYoutubeVideoId = exports.validateYoutubeUrl = exports.splitAtUsernamesAndLinks = exports.isVersionAGreaterOrEqualToVersionB = exports.generateUserGroupValue = exports.generateUserGroup = exports.isBaseExerciseTemplate = exports.getStrengthLevelFromPercentile = exports.numberToLocaleString = exports.numberWithCommas = exports.setVolume = exports.oneRepMax = exports.oneRepMaxPercentageMap = exports.getEstimatedExercisesDurationSeconds = exports.ESTIMATED_REST_TIMER_DURATION = exports.ESTIMATED_SET_DURATION = exports.UserFacingIndicatorToSetIndicator = exports.workoutSetCount = exports.userExerciseSetWeight = exports.workoutDistanceMeters = exports.workoutReps = exports.workoutDurationSeconds = exports.removeAccents = exports.getClosestDataPointAroundTargetDate = exports.getClosestDataPointBeforeTargetDate = exports.findMapped = exports.stringToNumber = exports.forceStringToNumber = exports.formatDurationInput = exports.isValidFormattedTime = exports.isWholeNumber = exports.isNumber = exports.isValidUuid = exports.isValidPhoneNumber = exports.isValidWebUrl = exports.URL_REGEX = exports.isValidEmail = exports.secondsToWordFormatMinutes = exports.secondsToWordFormat = exports.secondsToClockFormat = exports.secondsToClockParts = exports.isValidUsername = exports.roundToWholeNumber = exports.roundToOneDecimal = exports.roundToTwoDecimal = exports.divide = exports.clampNumber = exports.num = void 0;
7
- exports.rawInstructionsToIndexedSteps = exports.weekdayNumberMap = void 0;
7
+ exports.rawInstructionsToIndexedSteps = exports.formatSetValue = exports.exerciseWeight = exports.distance = exports.weekdayNumberMap = void 0;
8
8
  const dayjs_1 = __importDefault(require("dayjs"));
9
+ const _1 = require(".");
9
10
  /**
10
11
  * Doesn't matter what you throw in the function it'll
11
12
  * always return a number. Non number values will return
@@ -775,6 +776,64 @@ exports.weekdayNumberMap = {
775
776
  friday: 5,
776
777
  saturday: 6,
777
778
  };
779
+ const distance = (value, distanceUnit) => {
780
+ if (distanceUnit === 'miles') {
781
+ const unrounded = (0, _1.exactMetersToMiles)(value);
782
+ return (0, exports.roundToTwoDecimal)(unrounded);
783
+ }
784
+ return (0, exports.roundToTwoDecimal)(value / 1000);
785
+ };
786
+ exports.distance = distance;
787
+ const exerciseWeight = (value, weightUnit) => {
788
+ if (weightUnit === 'lbs') {
789
+ return (0, exports.roundToTwoDecimal)((0, _1.exactKgtoLbs)(value));
790
+ }
791
+ return (0, exports.roundToTwoDecimal)(value);
792
+ };
793
+ exports.exerciseWeight = exerciseWeight;
794
+ const formatSetValue = ({ exerciseType, set, units, labels, }) => {
795
+ var _a, _b, _c;
796
+ switch (exerciseType) {
797
+ case 'weight_reps':
798
+ case 'bodyweight_reps': {
799
+ const weight = `${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${labels.weight}`;
800
+ const reps = `${(_a = set.reps) !== null && _a !== void 0 ? _a : 0}`;
801
+ return `${weight} x ${reps}`;
802
+ }
803
+ case 'bodyweight_assisted_reps': {
804
+ const weight = `-${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${labels.weight}`;
805
+ const reps = `${(_b = set.reps) !== null && _b !== void 0 ? _b : 0}`;
806
+ return `${weight} x ${reps}`;
807
+ }
808
+ case 'reps_only':
809
+ return `${(_c = set.reps) !== null && _c !== void 0 ? _c : 0}`;
810
+ case 'distance_duration': {
811
+ const distanceValue = `${(0, exports.distance)(set.distance_meters || 0, units.distance)}${labels.distance}`;
812
+ const duration = `${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
813
+ return `${distanceValue} - ${duration}`;
814
+ }
815
+ case 'duration':
816
+ return (0, exports.secondsToWordFormat)(Number(set.duration_seconds));
817
+ case 'short_distance_weight': {
818
+ const weight = `${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${labels.weight}`;
819
+ const shortDistance = (0, exports.roundToTwoDecimal)(set.distance_meters || 0);
820
+ return `${weight} - ${shortDistance}${labels.shortDistance}`;
821
+ }
822
+ case 'weight_duration': {
823
+ const weight = `${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${labels.weight}`;
824
+ const duration = `${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
825
+ return `${weight} - ${duration}`;
826
+ }
827
+ case 'floors_duration':
828
+ return `${set.custom_metric ? (0, exports.roundToWholeNumber)(set.custom_metric) : 0} ${labels.floors} - ${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
829
+ case 'steps_duration':
830
+ return `${set.custom_metric ? (0, exports.roundToWholeNumber)(set.custom_metric) : 0} ${labels.steps} - ${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
831
+ default:
832
+ (0, _1.exhaustiveTypeCheck)(exerciseType);
833
+ return '';
834
+ }
835
+ };
836
+ exports.formatSetValue = formatSetValue;
778
837
  const rawInstructionsToIndexedSteps = (rawInstructions) => {
779
838
  const instructionsByLine = rawInstructions
780
839
  .split('\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.875",
3
+ "version": "1.0.877",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",