fit-file-parser 2.2.6 → 2.3.1

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/dist/binary.js CHANGED
@@ -5,6 +5,7 @@ const CompressedLocalMsgNumMask = 0x60;
5
5
  const CompressedHeaderMask = 0x80;
6
6
  const GarminTimeOffset = 631065600000;
7
7
  let monitoring_timestamp = 0;
8
+ console.log(">>> LOADING BINARY.TS <<<");
8
9
  export function addEndian(littleEndian, bytes) {
9
10
  let result = 0;
10
11
  if (!littleEndian)
@@ -106,7 +107,13 @@ function formatByType(data, type, scale, offset) {
106
107
  case 'uint16_array':
107
108
  case 'uint8_array':
108
109
  if (Array.isArray(data)) {
109
- return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
110
+ const baseType = type.replace('_array', '');
111
+ return data.map((dataItem) => {
112
+ if (isInvalidValue(dataItem, baseType)) {
113
+ return null;
114
+ }
115
+ return scale ? dataItem / scale + offset : dataItem;
116
+ });
110
117
  }
111
118
  return scale ? data / scale + offset : data;
112
119
  default:
@@ -11,6 +11,7 @@ const CompressedLocalMsgNumMask = 0x60;
11
11
  const CompressedHeaderMask = 0x80;
12
12
  const GarminTimeOffset = 631065600000;
13
13
  let monitoring_timestamp = 0;
14
+ console.log(">>> LOADING BINARY.TS <<<");
14
15
  function addEndian(littleEndian, bytes) {
15
16
  let result = 0;
16
17
  if (!littleEndian)
@@ -112,7 +113,13 @@ function formatByType(data, type, scale, offset) {
112
113
  case 'uint16_array':
113
114
  case 'uint8_array':
114
115
  if (Array.isArray(data)) {
115
- return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
116
+ const baseType = type.replace('_array', '');
117
+ return data.map((dataItem) => {
118
+ if (isInvalidValue(dataItem, baseType)) {
119
+ return null;
120
+ }
121
+ return scale ? dataItem / scale + offset : dataItem;
122
+ });
116
123
  }
117
124
  return scale ? data / scale + offset : data;
118
125
  default:
@@ -103,6 +103,7 @@ class FitParser {
103
103
  const tank_summaries = [];
104
104
  const jumps = [];
105
105
  const time_in_zone = [];
106
+ const activity_metrics = [];
106
107
  let loopIndex = headerLength;
107
108
  const messageTypes = [];
108
109
  const developerFields = [];
@@ -204,6 +205,9 @@ class FitParser {
204
205
  case 'time_in_zone':
205
206
  time_in_zone.push(message);
206
207
  break;
208
+ case 'activity_metrics':
209
+ activity_metrics.push(message);
210
+ break;
207
211
  default:
208
212
  if (messageType !== '') {
209
213
  fitObj[messageType] = message;
@@ -225,6 +229,7 @@ class FitParser {
225
229
  fitObj.tank_summaries = tank_summaries;
226
230
  fitObj.jumps = jumps;
227
231
  fitObj.time_in_zone = time_in_zone;
232
+ fitObj.activity_metrics = activity_metrics;
228
233
  if (isCascadeNeeded) {
229
234
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'records', records);
230
235
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'lengths', lengths);
package/dist/cjs/fit.js CHANGED
@@ -4190,6 +4190,93 @@ exports.FIT = {
4190
4190
  },
4191
4191
  0: { field: 'enabled', type: 'byte', scale: null, offset: 0, units: '' },
4192
4192
  },
4193
+ 140: {
4194
+ name: 'activity_metrics',
4195
+ 1: {
4196
+ field: 'new_max_heart_rate',
4197
+ type: 'uint8',
4198
+ scale: null,
4199
+ offset: 0,
4200
+ units: 'bpm',
4201
+ },
4202
+ 4: {
4203
+ field: 'aerobic_training_effect',
4204
+ type: 'uint8',
4205
+ scale: 10,
4206
+ offset: 0,
4207
+ units: '',
4208
+ },
4209
+ 7: {
4210
+ field: 'vo2_max',
4211
+ type: 'uint32',
4212
+ scale: 65536 / 3.5,
4213
+ offset: 0,
4214
+ units: 'ml/kg/min',
4215
+ },
4216
+ 9: {
4217
+ field: 'recovery_time',
4218
+ type: 'uint16',
4219
+ scale: null,
4220
+ offset: 0,
4221
+ units: 'min',
4222
+ },
4223
+ 11: {
4224
+ field: 'sport',
4225
+ type: 'sport',
4226
+ scale: null,
4227
+ offset: 0,
4228
+ units: '',
4229
+ },
4230
+ 20: {
4231
+ field: 'anaerobic_training_effect',
4232
+ type: 'uint8',
4233
+ scale: 10,
4234
+ offset: 0,
4235
+ units: '',
4236
+ },
4237
+ 29: {
4238
+ field: 'first_vo2_max',
4239
+ type: 'uint32',
4240
+ scale: 65536 / 3.5,
4241
+ offset: 0,
4242
+ units: 'ml/kg/min',
4243
+ },
4244
+ 41: {
4245
+ field: 'primary_benefit',
4246
+ type: 'uint8',
4247
+ scale: null,
4248
+ offset: 0,
4249
+ units: '',
4250
+ },
4251
+ 60: {
4252
+ field: 'total_ascent',
4253
+ type: 'uint16',
4254
+ scale: null,
4255
+ offset: 0,
4256
+ units: 'm',
4257
+ },
4258
+ 61: {
4259
+ field: 'total_descent',
4260
+ type: 'uint16',
4261
+ scale: null,
4262
+ offset: 0,
4263
+ units: 'm',
4264
+ },
4265
+ 62: {
4266
+ field: 'avg_power',
4267
+ type: 'uint16',
4268
+ scale: null,
4269
+ offset: 0,
4270
+ units: 'watts',
4271
+ },
4272
+ 63: {
4273
+ field: 'avg_heart_rate',
4274
+ type: 'uint8',
4275
+ scale: null,
4276
+ offset: 0,
4277
+ units: 'bpm',
4278
+ },
4279
+ },
4193
4280
  206: {
4194
4281
  name: 'field_description',
4195
4282
  0: {
@@ -4705,11 +4792,11 @@ exports.FIT = {
4705
4792
  units: 's',
4706
4793
  },
4707
4794
  5: {
4708
- field: 'hr_zone_high_boundary_deprecated',
4709
- type: 'uint8_array',
4710
- scale: null,
4795
+ field: 'time_in_power_zone',
4796
+ type: 'uint32_array',
4797
+ scale: 1000,
4711
4798
  offset: 0,
4712
- units: '',
4799
+ units: 's',
4713
4800
  },
4714
4801
  6: {
4715
4802
  field: 'hr_zone_high_boundary',
@@ -4733,11 +4820,11 @@ exports.FIT = {
4733
4820
  units: 'watts',
4734
4821
  },
4735
4822
  9: {
4736
- field: 'hr_calc_type',
4737
- type: 'hr_zone_calc',
4823
+ field: 'power_zone_high_boundary',
4824
+ type: 'uint16_array',
4738
4825
  scale: null,
4739
4826
  offset: 0,
4740
- units: '',
4827
+ units: 'watts',
4741
4828
  },
4742
4829
  10: {
4743
4830
  field: 'max_heart_rate_deprecated',
@@ -4855,6 +4942,7 @@ exports.FIT = {
4855
4942
  129: 'weather_alert',
4856
4943
  131: 'cadence_zone',
4857
4944
  132: 'hr',
4945
+ 140: 'activity_metrics',
4858
4946
  142: 'segment_lap',
4859
4947
  145: 'memo_glob',
4860
4948
  148: 'segment_id',
@@ -16,7 +16,7 @@ export interface MessageIndex {
16
16
  selected: boolean;
17
17
  }
18
18
  export type File = 'device' | 'settings' | 'sport' | 'activity' | 'workout' | 'course' | 'schedules' | 'weight' | 'totals' | 'goals' | 'blood_pressure' | 'monitoring_a' | 'activity_summary' | 'monitoring_daily' | 'monitoring_b' | 'segment' | 'segment_list' | 'exd_configuration' | 'mfg_range_min' | 'mfg_range_max';
19
- export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
19
+ export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
20
20
  export type Checksum = 'clear' | 'ok';
21
21
  export type FileFlags = '0' | 'read' | 'write' | 'erase';
22
22
  export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type';
@@ -72,9 +72,9 @@ export type Schedule = 'workout' | 'course';
72
72
  export type CoursePoint = 'generic' | 'summit' | 'valley' | 'water' | 'food' | 'danger' | 'left' | 'right' | 'straight' | 'first_aid' | 'fourth_category' | 'third_category' | 'second_category' | 'first_category' | 'hors_category' | 'sprint' | 'left_fork' | 'right_fork' | 'middle_fork' | 'slight_left' | 'sharp_left' | 'slight_right' | 'sharp_right' | 'u_turn' | 'segment_start' | 'segment_end';
73
73
  export type Manufacturer = '0' | 'garmin' | 'garmin_fr405_antfs' | 'zephyr' | 'dayton' | 'idt' | 'srm' | 'quarq' | 'ibike' | 'saris' | 'spark_hk' | 'tanita' | 'echowell' | 'dynastream_oem' | 'nautilus' | 'dynastream' | 'timex' | 'metrigear' | 'xelic' | 'beurer' | 'cardiosport' | 'a_and_d' | 'hmm' | 'suunto' | 'thita_elektronik' | 'gpulse' | 'clean_mobile' | 'pedal_brain' | 'peaksware' | 'saxonar' | 'lemond_fitness' | 'dexcom' | 'wahoo_fitness' | 'octane_fitness' | 'archinoetics' | 'the_hurt_box' | 'citizen_systems' | 'magellan' | 'osynce' | 'holux' | 'concept2' | 'one_giant_leap' | 'ace_sensor' | 'brim_brothers' | 'xplova' | 'perception_digital' | 'bf1systems' | 'pioneer' | 'spantec' | 'metalogics' | '4iiiis' | 'seiko_epson' | 'seiko_epson_oem' | 'ifor_powell' | 'maxwell_guider' | 'star_trac' | 'breakaway' | 'alatech_technology_ltd' | 'mio_technology_europe' | 'rotor' | 'geonaute' | 'id_bike' | 'specialized' | 'wtek' | 'physical_enterprises' | 'north_pole_engineering' | 'bkool' | 'cateye' | 'stages_cycling' | 'sigmasport' | 'tomtom' | 'peripedal' | 'wattbike' | 'moxy' | 'ciclosport' | 'powerbahn' | 'acorn_projects_aps' | 'lifebeam' | 'bontrager' | 'wellgo' | 'scosche' | 'magura' | 'woodway' | 'elite' | 'nielsen_kellerman' | 'dk_city' | 'tacx' | 'direction_technology' | 'magtonic' | '1partcarbon' | 'inside_ride_technologies' | 'sound_of_motion' | 'stryd' | 'icg' | 'mipulse' | 'bsx_athletics' | 'look' | 'campagnolo_srl' | 'body_bike_smart' | 'praxisworks' | 'limits_technology' | 'topaction_technology' | 'cosinuss' | 'fitcare' | 'magene' | 'giant_manufacturing_co' | 'tigrasport' | 'salutron' | 'technogym' | 'bryton_sensors' | 'latitude_limited' | 'soaring_technology' | 'igpsport' | 'thinkrider' | 'gopher_sport' | 'waterrower' | 'orangetheory' | 'inpeak' | 'kinetic' | 'johnson_health_tech' | 'polar_electro' | 'seesense' | 'nci_technology' | 'development' | 'healthandlife' | 'lezyne' | 'scribe_labs' | 'zwift' | 'watteam' | 'recon' | 'favero_electronics' | 'dynovelo' | 'strava' | 'precor' | 'bryton' | 'sram' | 'navman' | 'cobi' | 'spivi' | 'mio_magellan' | 'evesports' | 'sensitivus_gauge' | 'podoon' | 'life_time_fitness' | 'falco_e_motors' | 'minoura' | 'cycliq' | 'luxottica' | 'trainer_road' | 'the_sufferfest' | 'fullspeedahead' | 'virtualtraining' | 'feedbacksports' | 'omata' | 'vdo' | 'magneticdays' | 'hammerhead' | 'kinetic_by_kurt' | 'shapelog' | 'dabuziduo' | 'jetblack' | 'coros' | 'virtugo' | 'velosense' | 'actigraphcorp';
74
74
  export type GarminProduct = 'hrm_bike' | 'hrm1' | 'axh01' | 'axb01' | 'axb02' | 'hrm2ss' | 'dsi_alf02' | 'hrm3ss' | 'hrm_run_single_byte_product_id' | 'bsm' | 'bcm' | 'axs01' | 'hrm_tri_single_byte_product_id' | 'fr225_single_byte_product_id' | 'fr301_china' | 'fr301_japan' | 'fr301_korea' | 'fr301_taiwan' | 'fr405' | 'fr50' | 'fr405_japan' | 'fr60' | 'dsi_alf01' | 'fr310xt' | 'edge500' | 'fr110' | 'edge800' | 'edge500_taiwan' | 'edge500_japan' | 'chirp' | 'fr110_japan' | 'edge200' | 'fr910xt' | 'edge800_taiwan' | 'edge800_japan' | 'alf04' | 'fr610' | 'fr210_japan' | 'vector_ss' | 'vector_cp' | 'edge800_china' | 'edge500_china' | 'fr610_japan' | 'edge500_korea' | 'fr70' | 'fr310xt_4t' | 'amx' | 'fr10' | 'edge800_korea' | 'swim' | 'fr910xt_china' | 'fenix' | 'edge200_taiwan' | 'edge510' | 'edge810' | 'tempe' | 'fr910xt_japan' | 'fr620' | 'fr220' | 'fr910xt_korea' | 'fr10_japan' | 'edge810_japan' | 'virb_elite' | 'edge_touring' | 'edge510_japan' | 'hrm_tri' | 'hrm_run' | 'fr920xt' | 'edge510_asia' | 'edge810_china' | 'edge810_taiwan' | 'edge1000' | 'vivo_fit' | 'virb_remote' | 'vivo_ki' | 'fr15' | 'vivo_active' | 'edge510_korea' | 'fr620_japan' | 'fr620_china' | 'fr220_japan' | 'fr220_china' | 'approach_s6' | 'vivo_smart' | 'fenix2' | 'epix' | 'fenix3' | 'edge1000_taiwan' | 'edge1000_japan' | 'fr15_japan' | 'edge520' | 'edge1000_china' | 'fr620_russia' | 'fr220_russia' | 'vector_s' | 'edge1000_korea' | 'fr920xt_taiwan' | 'fr920xt_china' | 'fr920xt_japan' | 'virbx' | 'vivo_smart_apac' | 'etrex_touch' | 'edge25' | 'fr25' | 'vivo_fit2' | 'fr225' | 'fr630' | 'fr230' | 'fr735xt' | 'vivo_active_apac' | 'vector_2' | 'vector_2s' | 'virbxe' | 'fr620_taiwan' | 'fr220_taiwan' | 'truswing' | 'fenix3_china' | 'fenix3_twn' | 'varia_headlight' | 'varia_taillight_old' | 'edge_explore_1000' | 'fr225_asia' | 'varia_radar_taillight' | 'varia_radar_display' | 'edge20' | 'd2_bravo' | 'approach_s20' | 'varia_remote' | 'hrm4_run' | 'vivo_active_hr' | 'vivo_smart_hr' | 'vivo_move' | 'varia_vision' | 'vivo_fit3' | 'fenix3_hr' | 'virb_ultra_30' | 'index_smart_scale' | 'fr235' | 'fenix3_chronos' | 'oregon7xx' | 'rino7xx' | 'nautix' | 'edge_820' | 'edge_explore_820' | 'fenix5s' | 'd2_bravo_titanium' | 'varia_ut800' | 'running_dynamics_pod' | 'fenix5x' | 'vivo_fit_jr' | 'fr935' | 'fenix5' | 'descent' | 'sdm4' | 'edge_remote' | 'training_center' | 'connectiq_simulator' | 'android_antplus_plugin' | 'connect';
75
- export type AntplusDeviceType = '0' | 'antfs' | 'bike_power' | 'environment_sensor_legacy' | 'multi_sport_speed_distance' | 'control' | 'fitness_equipment' | 'blood_pressure' | 'geocache_node' | 'light_electric_vehicle' | 'env_sensor' | 'racquet' | 'control_hub' | 'muscle_oxygen' | 'shifting' | 'bike_light_main' | 'bike_light_shared' | 'exd' | 'bike_radar' | 'weight_scale' | 'heart_rate' | 'bike_speed_cadence' | 'bike_cadence' | 'bike_speed' | 'stride_speed_distance';
76
- export type LocalDeviceType = 'gps' | 'glonass' | 'beidou' | 'galileo' | 'waas_egnos' | 'local' | 'barometer' | 'accelerometer' | 'gyroscope' | 'compass';
77
- export type BleDeviceType = '0' | 'heart_rate' | 'cycling_speed_cadence' | 'cycling_power';
75
+ export type AntplusDeviceType = 'antfs' | 'bike_power' | 'environment_sensor_legacy' | 'multi_sport_speed_distance' | 'control' | 'fitness_equipment' | 'blood_pressure' | 'geocache_node' | 'light_electric_vehicle' | 'env_sensor' | 'racquet' | 'control_hub' | 'muscle_oxygen' | 'shifting' | 'bike_light_main' | 'bike_light_shared' | 'exd' | 'bike_radar' | 'bike_aero' | 'weight_scale' | 'heart_rate' | 'bike_speed_cadence' | 'bike_cadence' | 'bike_speed' | 'stride_speed_distance';
76
+ export type LocalDeviceType = 'gps' | 'glonass' | 'gps_glonass' | 'accelerometer' | 'barometer' | 'temperature' | 'whr' | 'sensor_hub';
77
+ export type BleDeviceType = 'connected_gps' | 'heart_rate' | 'bike_power' | 'bike_speed_cadence' | 'bike_speed' | 'bike_cadence' | 'footpod' | 'bike_trainer';
78
78
  export type AntNetwork = 'public' | 'antplus' | 'antfs' | 'private';
79
79
  export type WorkoutCapabilities = '0' | 'interval' | 'custom' | 'fitness_equipment' | 'firstbeat' | 'new_leaf' | 'tcx' | 'speed' | 'heart_rate' | 'distance' | 'cadence' | 'power' | 'grade' | 'resistance' | 'protected';
80
80
  export type BatteryStatus = '0' | 'new' | 'good' | 'ok' | 'low' | 'critical' | 'charging' | 'unknown';
@@ -667,7 +667,7 @@ export interface ParsedEvent {
667
667
  }
668
668
  export interface ParsedDeviceInfo {
669
669
  device_index?: number;
670
- device_type?: AntplusDeviceType;
670
+ device_type?: number;
671
671
  manufacturer?: Manufacturer;
672
672
  serial_number?: number;
673
673
  product?: number;
@@ -873,6 +873,20 @@ export interface ParsedOHrSettings {
873
873
  enabled?: number;
874
874
  timestamp: string;
875
875
  }
876
+ export interface ParsedActivityMetrics {
877
+ new_max_heart_rate?: number;
878
+ aerobic_training_effect?: number;
879
+ vo2_max?: number;
880
+ recovery_time?: number;
881
+ sport?: Sport;
882
+ anaerobic_training_effect?: number;
883
+ first_vo2_max?: number;
884
+ primary_benefit?: number;
885
+ total_ascent?: number;
886
+ total_descent?: number;
887
+ avg_power?: number;
888
+ avg_heart_rate?: number;
889
+ }
876
890
  export interface ParsedFieldDescription {
877
891
  developer_data_index?: number;
878
892
  field_definition_number?: number;
@@ -896,11 +910,9 @@ export interface ParsedTimeInZone {
896
910
  time_in_hr_zone?: number[];
897
911
  time_in_speed_zone?: number[];
898
912
  time_in_power_zone?: number[];
899
- hr_zone_high_boundary_deprecated?: number[];
900
913
  hr_zone_high_boundary?: number[];
901
914
  speed_zone_high_boundary?: number[];
902
915
  power_zone_high_boundary?: number[];
903
- hr_calc_type?: HrZoneCalc;
904
916
  max_heart_rate_deprecated?: number;
905
917
  max_heart_rate?: number;
906
918
  resting_heart_rate?: number;
@@ -1027,4 +1039,5 @@ export interface ParsedFit {
1027
1039
  tank_summaries?: ParsedTankSummary[];
1028
1040
  jumps?: ParsedJump[];
1029
1041
  time_in_zone?: ParsedTimeInZone[];
1042
+ activity_metrics?: ParsedActivityMetrics[];
1030
1043
  }
@@ -210,6 +210,7 @@ function generateFitType() {
210
210
  tank_summaries: 'parsed_tank_summary',
211
211
  jumps: 'parsed_jump',
212
212
  time_in_zone: 'parsed_time_in_zone',
213
+ activity_metrics: 'parsed_activity_metrics',
213
214
  };
214
215
  const referenceProperties = {
215
216
  file_creator: 'parsed_file_creator',
@@ -101,6 +101,7 @@ export default class FitParser {
101
101
  const tank_summaries = [];
102
102
  const jumps = [];
103
103
  const time_in_zone = [];
104
+ const activity_metrics = [];
104
105
  let loopIndex = headerLength;
105
106
  const messageTypes = [];
106
107
  const developerFields = [];
@@ -202,6 +203,9 @@ export default class FitParser {
202
203
  case 'time_in_zone':
203
204
  time_in_zone.push(message);
204
205
  break;
206
+ case 'activity_metrics':
207
+ activity_metrics.push(message);
208
+ break;
205
209
  default:
206
210
  if (messageType !== '') {
207
211
  fitObj[messageType] = message;
@@ -223,6 +227,7 @@ export default class FitParser {
223
227
  fitObj.tank_summaries = tank_summaries;
224
228
  fitObj.jumps = jumps;
225
229
  fitObj.time_in_zone = time_in_zone;
230
+ fitObj.activity_metrics = activity_metrics;
226
231
  if (isCascadeNeeded) {
227
232
  laps = mapDataIntoLap(laps, 'records', records);
228
233
  laps = mapDataIntoLap(laps, 'lengths', lengths);
package/dist/fit.js CHANGED
@@ -4187,6 +4187,93 @@ export const FIT = {
4187
4187
  },
4188
4188
  0: { field: 'enabled', type: 'byte', scale: null, offset: 0, units: '' },
4189
4189
  },
4190
+ 140: {
4191
+ name: 'activity_metrics',
4192
+ 1: {
4193
+ field: 'new_max_heart_rate',
4194
+ type: 'uint8',
4195
+ scale: null,
4196
+ offset: 0,
4197
+ units: 'bpm',
4198
+ },
4199
+ 4: {
4200
+ field: 'aerobic_training_effect',
4201
+ type: 'uint8',
4202
+ scale: 10,
4203
+ offset: 0,
4204
+ units: '',
4205
+ },
4206
+ 7: {
4207
+ field: 'vo2_max',
4208
+ type: 'uint32',
4209
+ scale: 65536 / 3.5,
4210
+ offset: 0,
4211
+ units: 'ml/kg/min',
4212
+ },
4213
+ 9: {
4214
+ field: 'recovery_time',
4215
+ type: 'uint16',
4216
+ scale: null,
4217
+ offset: 0,
4218
+ units: 'min',
4219
+ },
4220
+ 11: {
4221
+ field: 'sport',
4222
+ type: 'sport',
4223
+ scale: null,
4224
+ offset: 0,
4225
+ units: '',
4226
+ },
4227
+ 20: {
4228
+ field: 'anaerobic_training_effect',
4229
+ type: 'uint8',
4230
+ scale: 10,
4231
+ offset: 0,
4232
+ units: '',
4233
+ },
4234
+ 29: {
4235
+ field: 'first_vo2_max',
4236
+ type: 'uint32',
4237
+ scale: 65536 / 3.5,
4238
+ offset: 0,
4239
+ units: 'ml/kg/min',
4240
+ },
4241
+ 41: {
4242
+ field: 'primary_benefit',
4243
+ type: 'uint8',
4244
+ scale: null,
4245
+ offset: 0,
4246
+ units: '',
4247
+ },
4248
+ 60: {
4249
+ field: 'total_ascent',
4250
+ type: 'uint16',
4251
+ scale: null,
4252
+ offset: 0,
4253
+ units: 'm',
4254
+ },
4255
+ 61: {
4256
+ field: 'total_descent',
4257
+ type: 'uint16',
4258
+ scale: null,
4259
+ offset: 0,
4260
+ units: 'm',
4261
+ },
4262
+ 62: {
4263
+ field: 'avg_power',
4264
+ type: 'uint16',
4265
+ scale: null,
4266
+ offset: 0,
4267
+ units: 'watts',
4268
+ },
4269
+ 63: {
4270
+ field: 'avg_heart_rate',
4271
+ type: 'uint8',
4272
+ scale: null,
4273
+ offset: 0,
4274
+ units: 'bpm',
4275
+ },
4276
+ },
4190
4277
  206: {
4191
4278
  name: 'field_description',
4192
4279
  0: {
@@ -4702,11 +4789,11 @@ export const FIT = {
4702
4789
  units: 's',
4703
4790
  },
4704
4791
  5: {
4705
- field: 'hr_zone_high_boundary_deprecated',
4706
- type: 'uint8_array',
4707
- scale: null,
4792
+ field: 'time_in_power_zone',
4793
+ type: 'uint32_array',
4794
+ scale: 1000,
4708
4795
  offset: 0,
4709
- units: '',
4796
+ units: 's',
4710
4797
  },
4711
4798
  6: {
4712
4799
  field: 'hr_zone_high_boundary',
@@ -4730,11 +4817,11 @@ export const FIT = {
4730
4817
  units: 'watts',
4731
4818
  },
4732
4819
  9: {
4733
- field: 'hr_calc_type',
4734
- type: 'hr_zone_calc',
4820
+ field: 'power_zone_high_boundary',
4821
+ type: 'uint16_array',
4735
4822
  scale: null,
4736
4823
  offset: 0,
4737
- units: '',
4824
+ units: 'watts',
4738
4825
  },
4739
4826
  10: {
4740
4827
  field: 'max_heart_rate_deprecated',
@@ -4852,6 +4939,7 @@ export const FIT = {
4852
4939
  129: 'weather_alert',
4853
4940
  131: 'cadence_zone',
4854
4941
  132: 'hr',
4942
+ 140: 'activity_metrics',
4855
4943
  142: 'segment_lap',
4856
4944
  145: 'memo_glob',
4857
4945
  148: 'segment_id',
@@ -16,7 +16,7 @@ export interface MessageIndex {
16
16
  selected: boolean;
17
17
  }
18
18
  export type File = 'device' | 'settings' | 'sport' | 'activity' | 'workout' | 'course' | 'schedules' | 'weight' | 'totals' | 'goals' | 'blood_pressure' | 'monitoring_a' | 'activity_summary' | 'monitoring_daily' | 'monitoring_b' | 'segment' | 'segment_list' | 'exd_configuration' | 'mfg_range_min' | 'mfg_range_max';
19
- export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
19
+ export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
20
20
  export type Checksum = 'clear' | 'ok';
21
21
  export type FileFlags = '0' | 'read' | 'write' | 'erase';
22
22
  export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type';
@@ -72,9 +72,9 @@ export type Schedule = 'workout' | 'course';
72
72
  export type CoursePoint = 'generic' | 'summit' | 'valley' | 'water' | 'food' | 'danger' | 'left' | 'right' | 'straight' | 'first_aid' | 'fourth_category' | 'third_category' | 'second_category' | 'first_category' | 'hors_category' | 'sprint' | 'left_fork' | 'right_fork' | 'middle_fork' | 'slight_left' | 'sharp_left' | 'slight_right' | 'sharp_right' | 'u_turn' | 'segment_start' | 'segment_end';
73
73
  export type Manufacturer = '0' | 'garmin' | 'garmin_fr405_antfs' | 'zephyr' | 'dayton' | 'idt' | 'srm' | 'quarq' | 'ibike' | 'saris' | 'spark_hk' | 'tanita' | 'echowell' | 'dynastream_oem' | 'nautilus' | 'dynastream' | 'timex' | 'metrigear' | 'xelic' | 'beurer' | 'cardiosport' | 'a_and_d' | 'hmm' | 'suunto' | 'thita_elektronik' | 'gpulse' | 'clean_mobile' | 'pedal_brain' | 'peaksware' | 'saxonar' | 'lemond_fitness' | 'dexcom' | 'wahoo_fitness' | 'octane_fitness' | 'archinoetics' | 'the_hurt_box' | 'citizen_systems' | 'magellan' | 'osynce' | 'holux' | 'concept2' | 'one_giant_leap' | 'ace_sensor' | 'brim_brothers' | 'xplova' | 'perception_digital' | 'bf1systems' | 'pioneer' | 'spantec' | 'metalogics' | '4iiiis' | 'seiko_epson' | 'seiko_epson_oem' | 'ifor_powell' | 'maxwell_guider' | 'star_trac' | 'breakaway' | 'alatech_technology_ltd' | 'mio_technology_europe' | 'rotor' | 'geonaute' | 'id_bike' | 'specialized' | 'wtek' | 'physical_enterprises' | 'north_pole_engineering' | 'bkool' | 'cateye' | 'stages_cycling' | 'sigmasport' | 'tomtom' | 'peripedal' | 'wattbike' | 'moxy' | 'ciclosport' | 'powerbahn' | 'acorn_projects_aps' | 'lifebeam' | 'bontrager' | 'wellgo' | 'scosche' | 'magura' | 'woodway' | 'elite' | 'nielsen_kellerman' | 'dk_city' | 'tacx' | 'direction_technology' | 'magtonic' | '1partcarbon' | 'inside_ride_technologies' | 'sound_of_motion' | 'stryd' | 'icg' | 'mipulse' | 'bsx_athletics' | 'look' | 'campagnolo_srl' | 'body_bike_smart' | 'praxisworks' | 'limits_technology' | 'topaction_technology' | 'cosinuss' | 'fitcare' | 'magene' | 'giant_manufacturing_co' | 'tigrasport' | 'salutron' | 'technogym' | 'bryton_sensors' | 'latitude_limited' | 'soaring_technology' | 'igpsport' | 'thinkrider' | 'gopher_sport' | 'waterrower' | 'orangetheory' | 'inpeak' | 'kinetic' | 'johnson_health_tech' | 'polar_electro' | 'seesense' | 'nci_technology' | 'development' | 'healthandlife' | 'lezyne' | 'scribe_labs' | 'zwift' | 'watteam' | 'recon' | 'favero_electronics' | 'dynovelo' | 'strava' | 'precor' | 'bryton' | 'sram' | 'navman' | 'cobi' | 'spivi' | 'mio_magellan' | 'evesports' | 'sensitivus_gauge' | 'podoon' | 'life_time_fitness' | 'falco_e_motors' | 'minoura' | 'cycliq' | 'luxottica' | 'trainer_road' | 'the_sufferfest' | 'fullspeedahead' | 'virtualtraining' | 'feedbacksports' | 'omata' | 'vdo' | 'magneticdays' | 'hammerhead' | 'kinetic_by_kurt' | 'shapelog' | 'dabuziduo' | 'jetblack' | 'coros' | 'virtugo' | 'velosense' | 'actigraphcorp';
74
74
  export type GarminProduct = 'hrm_bike' | 'hrm1' | 'axh01' | 'axb01' | 'axb02' | 'hrm2ss' | 'dsi_alf02' | 'hrm3ss' | 'hrm_run_single_byte_product_id' | 'bsm' | 'bcm' | 'axs01' | 'hrm_tri_single_byte_product_id' | 'fr225_single_byte_product_id' | 'fr301_china' | 'fr301_japan' | 'fr301_korea' | 'fr301_taiwan' | 'fr405' | 'fr50' | 'fr405_japan' | 'fr60' | 'dsi_alf01' | 'fr310xt' | 'edge500' | 'fr110' | 'edge800' | 'edge500_taiwan' | 'edge500_japan' | 'chirp' | 'fr110_japan' | 'edge200' | 'fr910xt' | 'edge800_taiwan' | 'edge800_japan' | 'alf04' | 'fr610' | 'fr210_japan' | 'vector_ss' | 'vector_cp' | 'edge800_china' | 'edge500_china' | 'fr610_japan' | 'edge500_korea' | 'fr70' | 'fr310xt_4t' | 'amx' | 'fr10' | 'edge800_korea' | 'swim' | 'fr910xt_china' | 'fenix' | 'edge200_taiwan' | 'edge510' | 'edge810' | 'tempe' | 'fr910xt_japan' | 'fr620' | 'fr220' | 'fr910xt_korea' | 'fr10_japan' | 'edge810_japan' | 'virb_elite' | 'edge_touring' | 'edge510_japan' | 'hrm_tri' | 'hrm_run' | 'fr920xt' | 'edge510_asia' | 'edge810_china' | 'edge810_taiwan' | 'edge1000' | 'vivo_fit' | 'virb_remote' | 'vivo_ki' | 'fr15' | 'vivo_active' | 'edge510_korea' | 'fr620_japan' | 'fr620_china' | 'fr220_japan' | 'fr220_china' | 'approach_s6' | 'vivo_smart' | 'fenix2' | 'epix' | 'fenix3' | 'edge1000_taiwan' | 'edge1000_japan' | 'fr15_japan' | 'edge520' | 'edge1000_china' | 'fr620_russia' | 'fr220_russia' | 'vector_s' | 'edge1000_korea' | 'fr920xt_taiwan' | 'fr920xt_china' | 'fr920xt_japan' | 'virbx' | 'vivo_smart_apac' | 'etrex_touch' | 'edge25' | 'fr25' | 'vivo_fit2' | 'fr225' | 'fr630' | 'fr230' | 'fr735xt' | 'vivo_active_apac' | 'vector_2' | 'vector_2s' | 'virbxe' | 'fr620_taiwan' | 'fr220_taiwan' | 'truswing' | 'fenix3_china' | 'fenix3_twn' | 'varia_headlight' | 'varia_taillight_old' | 'edge_explore_1000' | 'fr225_asia' | 'varia_radar_taillight' | 'varia_radar_display' | 'edge20' | 'd2_bravo' | 'approach_s20' | 'varia_remote' | 'hrm4_run' | 'vivo_active_hr' | 'vivo_smart_hr' | 'vivo_move' | 'varia_vision' | 'vivo_fit3' | 'fenix3_hr' | 'virb_ultra_30' | 'index_smart_scale' | 'fr235' | 'fenix3_chronos' | 'oregon7xx' | 'rino7xx' | 'nautix' | 'edge_820' | 'edge_explore_820' | 'fenix5s' | 'd2_bravo_titanium' | 'varia_ut800' | 'running_dynamics_pod' | 'fenix5x' | 'vivo_fit_jr' | 'fr935' | 'fenix5' | 'descent' | 'sdm4' | 'edge_remote' | 'training_center' | 'connectiq_simulator' | 'android_antplus_plugin' | 'connect';
75
- export type AntplusDeviceType = '0' | 'antfs' | 'bike_power' | 'environment_sensor_legacy' | 'multi_sport_speed_distance' | 'control' | 'fitness_equipment' | 'blood_pressure' | 'geocache_node' | 'light_electric_vehicle' | 'env_sensor' | 'racquet' | 'control_hub' | 'muscle_oxygen' | 'shifting' | 'bike_light_main' | 'bike_light_shared' | 'exd' | 'bike_radar' | 'weight_scale' | 'heart_rate' | 'bike_speed_cadence' | 'bike_cadence' | 'bike_speed' | 'stride_speed_distance';
76
- export type LocalDeviceType = 'gps' | 'glonass' | 'beidou' | 'galileo' | 'waas_egnos' | 'local' | 'barometer' | 'accelerometer' | 'gyroscope' | 'compass';
77
- export type BleDeviceType = '0' | 'heart_rate' | 'cycling_speed_cadence' | 'cycling_power';
75
+ export type AntplusDeviceType = 'antfs' | 'bike_power' | 'environment_sensor_legacy' | 'multi_sport_speed_distance' | 'control' | 'fitness_equipment' | 'blood_pressure' | 'geocache_node' | 'light_electric_vehicle' | 'env_sensor' | 'racquet' | 'control_hub' | 'muscle_oxygen' | 'shifting' | 'bike_light_main' | 'bike_light_shared' | 'exd' | 'bike_radar' | 'bike_aero' | 'weight_scale' | 'heart_rate' | 'bike_speed_cadence' | 'bike_cadence' | 'bike_speed' | 'stride_speed_distance';
76
+ export type LocalDeviceType = 'gps' | 'glonass' | 'gps_glonass' | 'accelerometer' | 'barometer' | 'temperature' | 'whr' | 'sensor_hub';
77
+ export type BleDeviceType = 'connected_gps' | 'heart_rate' | 'bike_power' | 'bike_speed_cadence' | 'bike_speed' | 'bike_cadence' | 'footpod' | 'bike_trainer';
78
78
  export type AntNetwork = 'public' | 'antplus' | 'antfs' | 'private';
79
79
  export type WorkoutCapabilities = '0' | 'interval' | 'custom' | 'fitness_equipment' | 'firstbeat' | 'new_leaf' | 'tcx' | 'speed' | 'heart_rate' | 'distance' | 'cadence' | 'power' | 'grade' | 'resistance' | 'protected';
80
80
  export type BatteryStatus = '0' | 'new' | 'good' | 'ok' | 'low' | 'critical' | 'charging' | 'unknown';
@@ -667,7 +667,7 @@ export interface ParsedEvent {
667
667
  }
668
668
  export interface ParsedDeviceInfo {
669
669
  device_index?: number;
670
- device_type?: AntplusDeviceType;
670
+ device_type?: number;
671
671
  manufacturer?: Manufacturer;
672
672
  serial_number?: number;
673
673
  product?: number;
@@ -873,6 +873,20 @@ export interface ParsedOHrSettings {
873
873
  enabled?: number;
874
874
  timestamp: string;
875
875
  }
876
+ export interface ParsedActivityMetrics {
877
+ new_max_heart_rate?: number;
878
+ aerobic_training_effect?: number;
879
+ vo2_max?: number;
880
+ recovery_time?: number;
881
+ sport?: Sport;
882
+ anaerobic_training_effect?: number;
883
+ first_vo2_max?: number;
884
+ primary_benefit?: number;
885
+ total_ascent?: number;
886
+ total_descent?: number;
887
+ avg_power?: number;
888
+ avg_heart_rate?: number;
889
+ }
876
890
  export interface ParsedFieldDescription {
877
891
  developer_data_index?: number;
878
892
  field_definition_number?: number;
@@ -896,11 +910,9 @@ export interface ParsedTimeInZone {
896
910
  time_in_hr_zone?: number[];
897
911
  time_in_speed_zone?: number[];
898
912
  time_in_power_zone?: number[];
899
- hr_zone_high_boundary_deprecated?: number[];
900
913
  hr_zone_high_boundary?: number[];
901
914
  speed_zone_high_boundary?: number[];
902
915
  power_zone_high_boundary?: number[];
903
- hr_calc_type?: HrZoneCalc;
904
916
  max_heart_rate_deprecated?: number;
905
917
  max_heart_rate?: number;
906
918
  resting_heart_rate?: number;
@@ -1027,4 +1039,5 @@ export interface ParsedFit {
1027
1039
  tank_summaries?: ParsedTankSummary[];
1028
1040
  jumps?: ParsedJump[];
1029
1041
  time_in_zone?: ParsedTimeInZone[];
1042
+ activity_metrics?: ParsedActivityMetrics[];
1030
1043
  }
@@ -190,6 +190,7 @@ export function generateFitType() {
190
190
  tank_summaries: 'parsed_tank_summary',
191
191
  jumps: 'parsed_jump',
192
192
  time_in_zone: 'parsed_time_in_zone',
193
+ activity_metrics: 'parsed_activity_metrics',
193
194
  };
194
195
  const referenceProperties = {
195
196
  file_creator: 'parsed_file_creator',
@@ -0,0 +1,34 @@
1
+
2
+ import FitParser from './src/fit-parser';
3
+ import * as fs from 'fs';
4
+
5
+ const filePath = 'examples/road-with-power.fit';
6
+
7
+ async function run() {
8
+ try {
9
+ const content = fs.readFileSync(filePath);
10
+ const parser = new FitParser({
11
+ force: true,
12
+ });
13
+
14
+ parser.parse(content, (error: any, data: any) => {
15
+ if (error) {
16
+ console.error(error);
17
+ } else {
18
+ console.log('Parsed successfully');
19
+ // Check sessions for power zone data
20
+ if (data.sessions && data.sessions.length > 0) {
21
+ const session = data.sessions[0];
22
+ console.log('Session keys:', Object.keys(session));
23
+ console.log('time_in_power_zone:', session.time_in_power_zone);
24
+ console.log('time_in_hr_zone:', session.time_in_hr_zone);
25
+ console.log('time_in_speed_zone:', session.time_in_speed_zone);
26
+ }
27
+ }
28
+ });
29
+ } catch (e) {
30
+ console.error(e);
31
+ }
32
+ }
33
+
34
+ run();