fit-file-parser 6.1.1 → 6.2.0

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.
@@ -261,6 +261,7 @@ function generateFitType() {
261
261
  time_in_zone: 'parsed_time_in_zone',
262
262
  activity_metrics: 'parsed_activity_metrics',
263
263
  user_metrics: 'parsed_user_metrics',
264
+ batteries: 'parsed_battery',
264
265
  };
265
266
  const referenceProperties = {
266
267
  file_creator: 'parsed_file_creator',
@@ -28,5 +28,13 @@ export default class FitParser {
28
28
  private readonly options;
29
29
  constructor(options?: FitParserOptions);
30
30
  parseAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit>;
31
+ /**
32
+ * Parses every complete FIT file in an input, preserving file order and
33
+ * independent decoder state. Rejects incomplete files and trailing bytes;
34
+ * each file uses this parser's options, including its CRC policy.
35
+ * Rejects with an Error when any file cannot be parsed.
36
+ */
37
+ parseFilesAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit[]>;
31
38
  parse(content: ArrayBuffer | Buffer<ArrayBuffer>, callback: FitParserCallback): void;
39
+ private parseFile;
32
40
  }
@@ -1,3 +1,12 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { calculateCRC, readRecord } from './binary.js';
2
11
  import { mapDataIntoLap, mapDataIntoSession } from './helper.js';
3
12
  export { FitBaseType, FitEncoder } from './fit-encoder.js';
@@ -32,7 +41,71 @@ export default class FitParser {
32
41
  });
33
42
  });
34
43
  }
44
+ /**
45
+ * Parses every complete FIT file in an input, preserving file order and
46
+ * independent decoder state. Rejects incomplete files and trailing bytes;
47
+ * each file uses this parser's options, including its CRC policy.
48
+ * Rejects with an Error when any file cannot be parsed.
49
+ */
50
+ parseFilesAsync(content) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ const blob = content instanceof ArrayBuffer
53
+ ? new Uint8Array(content)
54
+ : new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
55
+ const results = [];
56
+ let start = 0;
57
+ do {
58
+ const remaining = blob.length - start;
59
+ if (remaining < 12) {
60
+ throw new Error('File to small to be a FIT file');
61
+ }
62
+ const headerLength = blob[start];
63
+ if (headerLength !== 12 && headerLength !== 14) {
64
+ throw new Error('Incorrect header size');
65
+ }
66
+ if (remaining < headerLength) {
67
+ throw new Error('File to small to be a FIT file');
68
+ }
69
+ if (blob[start + 8] !== 0x2E
70
+ || blob[start + 9] !== 0x46
71
+ || blob[start + 10] !== 0x49
72
+ || blob[start + 11] !== 0x54) {
73
+ throw new Error('Missing \'.FIT\' in header');
74
+ }
75
+ const dataLength = new DataView(blob.buffer, blob.byteOffset + start, remaining)
76
+ .getUint32(4, true);
77
+ const crcStart = start + headerLength + dataLength;
78
+ if (crcStart > blob.length) {
79
+ throw new Error('File data exceeds input length');
80
+ }
81
+ if (crcStart + 2 > blob.length) {
82
+ throw new Error('File CRC missing');
83
+ }
84
+ const end = crcStart + 2;
85
+ try {
86
+ results.push(yield new Promise((resolve, reject) => {
87
+ this.parseFile(blob.slice(start, end).buffer, (error, data) => {
88
+ if (error) {
89
+ reject(error);
90
+ }
91
+ else if (data) {
92
+ resolve(data);
93
+ }
94
+ }, true);
95
+ }));
96
+ }
97
+ catch (error) {
98
+ throw error instanceof Error ? error : new Error(String(error));
99
+ }
100
+ start = end;
101
+ } while (start < blob.length);
102
+ return results;
103
+ });
104
+ }
35
105
  parse(content, callback) {
106
+ this.parseFile(content, callback);
107
+ }
108
+ parseFile(content, callback, validateRecordBounds = false) {
36
109
  var _a, _b, _c;
37
110
  const blob = content instanceof ArrayBuffer
38
111
  ? new Uint8Array(content)
@@ -92,6 +165,7 @@ export default class FitParser {
92
165
  protocolVersion,
93
166
  };
94
167
  const messages = {};
168
+ const batteries = [];
95
169
  let sessions = [];
96
170
  let laps = [];
97
171
  const records = [];
@@ -141,6 +215,12 @@ export default class FitParser {
141
215
  let pausedTime = 0;
142
216
  while (loopIndex < crcStart) {
143
217
  const { globalMessageNumber, littleEndian, message, messageType, nextIndex, compressedTimestamp, rawFields: recordRawFields, rawDeveloperFields: recordRawDeveloperFields, unmappedFields: recordUnmappedFields, unmappedDeveloperFields: recordUnmappedDeveloperFields, } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
218
+ // Complete-file parsing also validates bounds without requiring raw
219
+ // retention. The single-file API keeps its existing recovery behavior.
220
+ if (validateRecordBounds
221
+ && (!Number.isInteger(nextIndex) || nextIndex <= loopIndex || nextIndex > crcStart)) {
222
+ throw new Error('Invalid FIT record bounds');
223
+ }
144
224
  loopIndex = nextIndex;
145
225
  if (globalMessageNumber !== undefined) {
146
226
  const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
@@ -191,6 +271,9 @@ export default class FitParser {
191
271
  ((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
192
272
  }
193
273
  switch (messageType) {
274
+ case 'battery':
275
+ batteries.push(message);
276
+ break;
194
277
  case 'lap':
195
278
  laps.push(message);
196
279
  break;
@@ -335,6 +418,7 @@ export default class FitParser {
335
418
  fitObj.time_in_zone = time_in_zone;
336
419
  fitObj.activity_metrics = activity_metrics;
337
420
  fitObj.user_metrics = user_metrics;
421
+ fitObj.batteries = batteries;
338
422
  fitObj.messages = messages;
339
423
  if (rawDeveloperFields) {
340
424
  fitObj.raw_developer_fields = rawDeveloperFields;
@@ -156,7 +156,7 @@ export type MaxMetCategory = 'generic' | 'cycling' | number;
156
156
  export type MaxMetHeartRateSource = 'whr' | 'hrm' | number;
157
157
  export type MaxMetSpeedSource = 'onboard_gps' | 'connected_gps' | 'cadence' | number;
158
158
  export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type' | number;
159
- 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' | 'training_settings' | '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' | 'user_metrics' | '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' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
159
+ 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' | 'training_settings' | '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' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'battery' | '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' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
160
160
  export interface MessageIndex {
161
161
  value: number;
162
162
  reserved: boolean;
@@ -498,6 +498,8 @@ export interface ParsedSession {
498
498
  best_lap_index?: number;
499
499
  min_altitude?: number;
500
500
  active_time?: number;
501
+ avg_strokes_per_length?: number;
502
+ avg_swolf?: number;
501
503
  player_score?: number;
502
504
  opponent_score?: number;
503
505
  opponent_name?: string;
@@ -522,6 +524,9 @@ export interface ParsedSession {
522
524
  avg_left_pedal_smoothness?: number;
523
525
  avg_right_pedal_smoothness?: number;
524
526
  avg_combined_pedal_smoothness?: number;
527
+ num_segment_laps?: number;
528
+ front_gear_shift_count?: number;
529
+ rear_gear_shift_count?: number;
525
530
  sport_profile_name?: string;
526
531
  sport_index?: number;
527
532
  time_standing?: number;
@@ -588,6 +593,7 @@ export interface ParsedSession {
588
593
  avg_core_temperature?: number;
589
594
  min_core_temperature?: number;
590
595
  max_core_temperature?: number;
596
+ avg_grade_adjusted_speed?: number;
591
597
  timestamp?: Date;
592
598
  message_index?: MessageIndex;
593
599
  laps?: ParsedLap[];
@@ -620,6 +626,10 @@ export interface ParsedLap {
620
626
  lap_trigger?: LapTrigger;
621
627
  sport?: Sport;
622
628
  event_group?: number;
629
+ nec_lat?: number;
630
+ nec_long?: number;
631
+ swc_lat?: number;
632
+ swc_long?: number;
623
633
  num_lengths?: number;
624
634
  normalized_power?: number;
625
635
  left_right_balance?: LeftRightBalance100;
@@ -653,6 +663,7 @@ export interface ParsedLap {
653
663
  min_heart_rate?: number;
654
664
  active_time?: number;
655
665
  wkt_step_index?: MessageIndex;
666
+ avg_swolf?: number;
656
667
  opponent_score?: number;
657
668
  stroke_count?: (number | null)[];
658
669
  zone_count?: (number | null)[];
@@ -669,11 +680,14 @@ export interface ParsedLap {
669
680
  avg_saturated_hemoglobin_percent?: (number | null)[];
670
681
  min_saturated_hemoglobin_percent?: (number | null)[];
671
682
  max_saturated_hemoglobin_percent?: (number | null)[];
683
+ avg_strokes_per_length?: number;
672
684
  avg_left_torque_effectiveness?: number;
673
685
  avg_right_torque_effectiveness?: number;
674
686
  avg_left_pedal_smoothness?: number;
675
687
  avg_right_pedal_smoothness?: number;
676
688
  avg_combined_pedal_smoothness?: number;
689
+ front_gear_shift_count?: number;
690
+ rear_gear_shift_count?: number;
677
691
  time_standing?: number;
678
692
  stand_count?: number;
679
693
  avg_left_pco?: number;
@@ -710,11 +724,13 @@ export interface ParsedLap {
710
724
  jump_count?: number;
711
725
  avg_grit?: number;
712
726
  avg_flow?: number;
727
+ metabolic_calories?: number;
713
728
  total_fractional_ascent?: number;
714
729
  total_fractional_descent?: number;
715
730
  avg_core_temperature?: number;
716
731
  min_core_temperature?: number;
717
732
  max_core_temperature?: number;
733
+ avg_grade_adjusted_speed?: number;
718
734
  timestamp?: Date;
719
735
  message_index?: MessageIndex;
720
736
  records?: ParsedRecord[];
@@ -1119,6 +1135,10 @@ export interface ParsedMonitoringInfo {
1119
1135
  resting_metabolic_rate?: number;
1120
1136
  timestamp?: Date;
1121
1137
  }
1138
+ export interface ParsedBattery {
1139
+ capacity?: number;
1140
+ timestamp?: Date;
1141
+ }
1122
1142
  export interface ParsedPad {
1123
1143
  }
1124
1144
  export interface ParsedSlaveDevice {
@@ -2007,6 +2027,7 @@ export interface ParsedMessages {
2007
2027
  ant_channel_id?: ParsedAntChannelId[];
2008
2028
  length?: ParsedLength[];
2009
2029
  monitoring_info?: ParsedMonitoringInfo[];
2030
+ battery?: ParsedBattery[];
2010
2031
  pad?: ParsedPad[];
2011
2032
  slave_device?: ParsedSlaveDevice[];
2012
2033
  connectivity?: ParsedConnectivity[];
@@ -2134,6 +2155,7 @@ export interface ParsedFit {
2134
2155
  time_in_zone?: ParsedTimeInZone[];
2135
2156
  activity_metrics?: ParsedActivityMetrics[];
2136
2157
  user_metrics?: ParsedUserMetrics[];
2158
+ batteries?: ParsedBattery[];
2137
2159
  capabilities?: ParsedCapabilities;
2138
2160
  hrm_profile?: ParsedHrmProfile;
2139
2161
  sdm_profile?: ParsedSdmProfile;
@@ -0,0 +1,12 @@
1
+ export type FitProfileValueMap = Record<number, string | number>;
2
+ /**
3
+ * Lookup-oriented slices of the maintained FIT profile.
4
+ *
5
+ * The full decoder composes these exact objects into PROFILE_TYPES, keeping
6
+ * this module authoritative without loading message definitions in lookup-only consumers.
7
+ */
8
+ export declare const FIT_PROFILE_COURSE_POINTS: FitProfileValueMap;
9
+ export declare const FIT_PROFILE_GARMIN_PRODUCTS: FitProfileValueMap;
10
+ export declare const FIT_PROFILE_MANUFACTURERS: FitProfileValueMap;
11
+ export declare const FIT_PROFILE_SPORTS: FitProfileValueMap;
12
+ export declare const FIT_PROFILE_SUB_SPORTS: FitProfileValueMap;