fit-file-parser 6.1.2 → 6.2.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.
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  exports.readFitUnsignedField = exports.readFitStringField = exports.readFitMessages = exports.getFitBaseTypeId = exports.fitTimestampToUnixMilliseconds = exports.FitMessageReaderError = exports.getFitSubSportName = exports.getFitSubSportId = exports.getFitSportName = exports.getFitSportId = exports.getFitManufacturerName = exports.getFitGarminProductName = exports.getFitGarminProductDisplayName = exports.getFitCoursePointId = exports.FitEncoder = exports.FitBaseType = void 0;
4
13
  const binary_js_1 = require("./binary.js");
@@ -22,21 +31,31 @@ Object.defineProperty(exports, "getFitBaseTypeId", { enumerable: true, get: func
22
31
  Object.defineProperty(exports, "readFitMessages", { enumerable: true, get: function () { return raw_message_reader_js_1.readFitMessages; } });
23
32
  Object.defineProperty(exports, "readFitStringField", { enumerable: true, get: function () { return raw_message_reader_js_1.readFitStringField; } });
24
33
  Object.defineProperty(exports, "readFitUnsignedField", { enumerable: true, get: function () { return raw_message_reader_js_1.readFitUnsignedField; } });
34
+ function hasFitHeader(blob, start) {
35
+ const headerLength = blob[start];
36
+ return blob.length - start >= headerLength
37
+ && (headerLength === 12 || headerLength === 14)
38
+ && blob[start + 8] === 0x2E
39
+ && blob[start + 9] === 0x46
40
+ && blob[start + 10] === 0x49
41
+ && blob[start + 11] === 0x54;
42
+ }
25
43
  class FitParser {
26
44
  constructor(options = {}) {
27
- var _a, _b, _c, _d;
45
+ var _a, _b, _c, _d, _e;
28
46
  this.options = {
29
47
  force: options.force != null ? options.force : true,
48
+ strict: (_a = options.strict) !== null && _a !== void 0 ? _a : false,
30
49
  speedUnit: options.speedUnit || 'm/s',
31
50
  lengthUnit: options.lengthUnit || 'm',
32
51
  temperatureUnit: options.temperatureUnit || 'celsius',
33
52
  elapsedRecordField: options.elapsedRecordField || false,
34
53
  pressureUnit: options.pressureUnit || 'bar',
35
54
  mode: options.mode || 'list',
36
- includeRawDeveloperFields: (_a = options.includeRawDeveloperFields) !== null && _a !== void 0 ? _a : false,
37
- includeRawMessages: (_b = options.includeRawMessages) !== null && _b !== void 0 ? _b : false,
38
- includeUnmappedMessages: (_c = options.includeUnmappedMessages) !== null && _c !== void 0 ? _c : false,
39
- rawMessagesOnly: (_d = options.rawMessagesOnly) !== null && _d !== void 0 ? _d : false,
55
+ includeRawDeveloperFields: (_b = options.includeRawDeveloperFields) !== null && _b !== void 0 ? _b : false,
56
+ includeRawMessages: (_c = options.includeRawMessages) !== null && _c !== void 0 ? _c : false,
57
+ includeUnmappedMessages: (_d = options.includeUnmappedMessages) !== null && _d !== void 0 ? _d : false,
58
+ rawMessagesOnly: (_e = options.rawMessagesOnly) !== null && _e !== void 0 ? _e : false,
40
59
  };
41
60
  }
42
61
  parseAsync(content) {
@@ -51,7 +70,81 @@ class FitParser {
51
70
  });
52
71
  });
53
72
  }
73
+ /**
74
+ * Parses FIT files in input order with independent decoder state. Uses the
75
+ * same force and strict policies as parseAsync; strict mode rejects trailing
76
+ * bytes, while the default ignores a non-FIT suffix after parsed files.
77
+ * Rejects with an Error when any file cannot be parsed.
78
+ */
79
+ parseFilesAsync(content) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ const blob = content instanceof ArrayBuffer
82
+ ? new Uint8Array(content)
83
+ : new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
84
+ const results = [];
85
+ let start = 0;
86
+ do {
87
+ const remaining = blob.length - start;
88
+ const headerLength = blob[start];
89
+ if (results.length > 0 && !this.options.strict && !hasFitHeader(blob, start)) {
90
+ break;
91
+ }
92
+ if (remaining < 12) {
93
+ throw new Error('File to small to be a FIT file');
94
+ }
95
+ if (headerLength !== 12 && headerLength !== 14) {
96
+ throw new Error('Incorrect header size');
97
+ }
98
+ if (remaining < headerLength) {
99
+ throw new Error('File to small to be a FIT file');
100
+ }
101
+ if (blob[start + 8] !== 0x2E
102
+ || blob[start + 9] !== 0x46
103
+ || blob[start + 10] !== 0x49
104
+ || blob[start + 11] !== 0x54) {
105
+ throw new Error('Missing \'.FIT\' in header');
106
+ }
107
+ const dataLength = new DataView(blob.buffer, blob.byteOffset + start, remaining)
108
+ .getUint32(4, true);
109
+ const crcStart = start + headerLength + dataLength;
110
+ // File completeness and CRC checks belong to the shared parser below,
111
+ // preserving the same validation policy and error order in every API.
112
+ let end = Math.min(crcStart + 2, blob.length);
113
+ // Missing CRC bytes can also precede the next file. Check only the
114
+ // declared boundary and its one-byte CRC variant; never scan activity
115
+ // payloads for a guessed file boundary.
116
+ if (this.options.force && !this.options.strict) {
117
+ if (hasFitHeader(blob, crcStart)) {
118
+ end = crcStart;
119
+ }
120
+ else if (hasFitHeader(blob, crcStart + 1)) {
121
+ end = crcStart + 1;
122
+ }
123
+ }
124
+ try {
125
+ results.push(yield new Promise((resolve, reject) => {
126
+ this.parseFile(blob.slice(start, end).buffer, (error, data) => {
127
+ if (error) {
128
+ reject(error);
129
+ }
130
+ else if (data) {
131
+ resolve(data);
132
+ }
133
+ });
134
+ }));
135
+ }
136
+ catch (error) {
137
+ throw error instanceof Error ? error : new Error(String(error));
138
+ }
139
+ start = end;
140
+ } while (start < blob.length);
141
+ return results;
142
+ });
143
+ }
54
144
  parse(content, callback) {
145
+ this.parseFile(content, callback);
146
+ }
147
+ parseFile(content, callback) {
55
148
  var _a, _b, _c;
56
149
  const blob = content instanceof ArrayBuffer
57
150
  ? new Uint8Array(content)
@@ -94,10 +187,14 @@ class FitParser {
94
187
  callback('File data exceeds input length', undefined);
95
188
  return;
96
189
  }
97
- if (!this.options.force && crcStart + 2 > blob.length) {
190
+ if ((!this.options.force || this.options.strict) && crcStart + 2 > blob.length) {
98
191
  callback('File CRC missing', undefined);
99
192
  return;
100
193
  }
194
+ if (this.options.strict && crcStart + 2 < blob.length) {
195
+ callback('Unexpected bytes after FIT file', undefined);
196
+ return;
197
+ }
101
198
  if (!this.options.force) {
102
199
  const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
103
200
  const crcFileCalc = (0, binary_js_1.calculateCRC)(blob, 0, crcStart);
@@ -111,6 +208,7 @@ class FitParser {
111
208
  protocolVersion,
112
209
  };
113
210
  const messages = {};
211
+ const batteries = [];
114
212
  let sessions = [];
115
213
  let laps = [];
116
214
  const records = [];
@@ -160,6 +258,10 @@ class FitParser {
160
258
  let pausedTime = 0;
161
259
  while (loopIndex < crcStart) {
162
260
  const { globalMessageNumber, littleEndian, message, messageType, nextIndex, compressedTimestamp, rawFields: recordRawFields, rawDeveloperFields: recordRawDeveloperFields, unmappedFields: recordUnmappedFields, unmappedDeveloperFields: recordUnmappedDeveloperFields, } = (0, binary_js_1.readRecord)(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
261
+ if (this.options.strict
262
+ && (!Number.isInteger(nextIndex) || nextIndex <= loopIndex || nextIndex > crcStart)) {
263
+ throw new Error('Invalid FIT record bounds');
264
+ }
163
265
  loopIndex = nextIndex;
164
266
  if (globalMessageNumber !== undefined) {
165
267
  const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
@@ -210,6 +312,9 @@ class FitParser {
210
312
  ((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
211
313
  }
212
314
  switch (messageType) {
315
+ case 'battery':
316
+ batteries.push(message);
317
+ break;
213
318
  case 'lap':
214
319
  laps.push(message);
215
320
  break;
@@ -354,6 +459,7 @@ class FitParser {
354
459
  fitObj.time_in_zone = time_in_zone;
355
460
  fitObj.activity_metrics = activity_metrics;
356
461
  fitObj.user_metrics = user_metrics;
462
+ fitObj.batteries = batteries;
357
463
  fitObj.messages = messages;
358
464
  if (rawDeveloperFields) {
359
465
  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;
@@ -1348,6 +1348,60 @@ exports.PROFILE_MESSAGES = {
1348
1348
  name: 'goal',
1349
1349
  },
1350
1350
  18: {
1351
+ 79: {
1352
+ field: 'avg_strokes_per_length',
1353
+ type: 'uint16',
1354
+ baseType: 'uint16',
1355
+ array: false,
1356
+ scale: 10,
1357
+ offset: 0,
1358
+ units: 'strokes/length',
1359
+ },
1360
+ 80: {
1361
+ field: 'avg_swolf',
1362
+ type: 'uint16',
1363
+ baseType: 'uint16',
1364
+ array: false,
1365
+ scale: 1,
1366
+ offset: 0,
1367
+ units: '',
1368
+ },
1369
+ 106: {
1370
+ field: 'num_segment_laps',
1371
+ type: 'uint16',
1372
+ baseType: 'uint16',
1373
+ array: false,
1374
+ scale: 1,
1375
+ offset: 0,
1376
+ units: '',
1377
+ },
1378
+ 107: {
1379
+ field: 'front_gear_shift_count',
1380
+ type: 'uint16',
1381
+ baseType: 'uint16',
1382
+ array: false,
1383
+ scale: 1,
1384
+ offset: 0,
1385
+ units: '',
1386
+ },
1387
+ 108: {
1388
+ field: 'rear_gear_shift_count',
1389
+ type: 'uint16',
1390
+ baseType: 'uint16',
1391
+ array: false,
1392
+ scale: 1,
1393
+ offset: 0,
1394
+ units: '',
1395
+ },
1396
+ 211: {
1397
+ field: 'avg_grade_adjusted_speed',
1398
+ type: 'uint32',
1399
+ baseType: 'uint32',
1400
+ array: false,
1401
+ scale: 1000,
1402
+ offset: 0,
1403
+ units: 'm/s',
1404
+ },
1351
1405
  0: {
1352
1406
  field: 'event',
1353
1407
  type: 'event',
@@ -2808,6 +2862,96 @@ exports.PROFILE_MESSAGES = {
2808
2862
  name: 'session',
2809
2863
  },
2810
2864
  19: {
2865
+ 27: {
2866
+ field: 'nec_lat',
2867
+ type: 'sint32',
2868
+ baseType: 'sint32',
2869
+ array: false,
2870
+ scale: 1,
2871
+ offset: 0,
2872
+ units: 'semicircles',
2873
+ },
2874
+ 28: {
2875
+ field: 'nec_long',
2876
+ type: 'sint32',
2877
+ baseType: 'sint32',
2878
+ array: false,
2879
+ scale: 1,
2880
+ offset: 0,
2881
+ units: 'semicircles',
2882
+ },
2883
+ 29: {
2884
+ field: 'swc_lat',
2885
+ type: 'sint32',
2886
+ baseType: 'sint32',
2887
+ array: false,
2888
+ scale: 1,
2889
+ offset: 0,
2890
+ units: 'semicircles',
2891
+ },
2892
+ 30: {
2893
+ field: 'swc_long',
2894
+ type: 'sint32',
2895
+ baseType: 'sint32',
2896
+ array: false,
2897
+ scale: 1,
2898
+ offset: 0,
2899
+ units: 'semicircles',
2900
+ },
2901
+ 73: {
2902
+ field: 'avg_swolf',
2903
+ type: 'uint16',
2904
+ baseType: 'uint16',
2905
+ array: false,
2906
+ scale: 1,
2907
+ offset: 0,
2908
+ units: '',
2909
+ },
2910
+ 90: {
2911
+ field: 'avg_strokes_per_length',
2912
+ type: 'uint16',
2913
+ baseType: 'uint16',
2914
+ array: false,
2915
+ scale: 10,
2916
+ offset: 0,
2917
+ units: 'strokes / length',
2918
+ },
2919
+ 96: {
2920
+ field: 'front_gear_shift_count',
2921
+ type: 'uint16',
2922
+ baseType: 'uint16',
2923
+ array: false,
2924
+ scale: 1,
2925
+ offset: 0,
2926
+ units: '',
2927
+ },
2928
+ 97: {
2929
+ field: 'rear_gear_shift_count',
2930
+ type: 'uint16',
2931
+ baseType: 'uint16',
2932
+ array: false,
2933
+ scale: 1,
2934
+ offset: 0,
2935
+ units: '',
2936
+ },
2937
+ 155: {
2938
+ field: 'metabolic_calories',
2939
+ type: 'uint16',
2940
+ baseType: 'uint16',
2941
+ array: false,
2942
+ scale: 1,
2943
+ offset: 0,
2944
+ units: 'kcal',
2945
+ },
2946
+ 161: {
2947
+ field: 'avg_grade_adjusted_speed',
2948
+ type: 'uint32',
2949
+ baseType: 'uint32',
2950
+ array: false,
2951
+ scale: 1000,
2952
+ offset: 0,
2953
+ units: 'm/s',
2954
+ },
2811
2955
  0: {
2812
2956
  field: 'event',
2813
2957
  type: 'event',
@@ -6986,6 +7130,27 @@ exports.PROFILE_MESSAGES = {
6986
7130
  },
6987
7131
  name: 'monitoring_info',
6988
7132
  },
7133
+ 104: {
7134
+ 2: {
7135
+ field: 'capacity',
7136
+ type: 'uint8',
7137
+ baseType: 'uint8',
7138
+ array: false,
7139
+ scale: 1,
7140
+ offset: 0,
7141
+ units: '%',
7142
+ },
7143
+ 253: {
7144
+ field: 'timestamp',
7145
+ type: 'date_time',
7146
+ baseType: 'uint32',
7147
+ array: false,
7148
+ scale: 1,
7149
+ offset: 0,
7150
+ units: '',
7151
+ },
7152
+ name: 'battery',
7153
+ },
6989
7154
  105: {
6990
7155
  name: 'pad',
6991
7156
  },
@@ -15266,6 +15431,7 @@ exports.PROFILE_TYPES = {
15266
15431
  82: 'ant_channel_id',
15267
15432
  101: 'length',
15268
15433
  103: 'monitoring_info',
15434
+ 104: 'battery',
15269
15435
  105: 'pad',
15270
15436
  106: 'slave_device',
15271
15437
  127: 'connectivity',
@@ -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',
@@ -7,7 +7,10 @@ export { getFitCoursePointId, getFitGarminProductDisplayName, getFitGarminProduc
7
7
  export { FitMessageReaderError, fitTimestampToUnixMilliseconds, getFitBaseTypeId, readFitMessages, readFitStringField, readFitUnsignedField, } from './raw-message-reader.js';
8
8
  export type { FitMessageReaderErrorCode, FitMessageReaderIssue, FitMessageReaderIssueCode, FitMessageReaderOptions, FitMessageReaderResult, FitRawDeveloperField, FitRawField, FitRawMessage, } from './raw-message-reader.js';
9
9
  export interface FitParserOptions {
10
+ /** Skips CRC validation and enables supported best-effort recovery. Defaults to true. */
10
11
  force?: boolean;
12
+ /** Requires complete files and records, and rejects trailing bytes. Defaults to false. */
13
+ strict?: boolean;
11
14
  speedUnit?: string;
12
15
  lengthUnit?: string;
13
16
  temperatureUnit?: string;
@@ -28,5 +31,13 @@ export default class FitParser {
28
31
  private readonly options;
29
32
  constructor(options?: FitParserOptions);
30
33
  parseAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit>;
34
+ /**
35
+ * Parses FIT files in input order with independent decoder state. Uses the
36
+ * same force and strict policies as parseAsync; strict mode rejects trailing
37
+ * bytes, while the default ignores a non-FIT suffix after parsed files.
38
+ * Rejects with an Error when any file cannot be parsed.
39
+ */
40
+ parseFilesAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit[]>;
31
41
  parse(content: ArrayBuffer | Buffer<ArrayBuffer>, callback: FitParserCallback): void;
42
+ private parseFile;
32
43
  }