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.
- package/CHANGELOG.md +48 -0
- package/PROFILE.md +39 -8
- package/README.md +49 -10
- package/dist/binary.js +25 -12
- package/dist/cjs/binary.js +25 -12
- package/dist/cjs/fit-parser.d.ts +8 -0
- package/dist/cjs/fit-parser.js +84 -0
- package/dist/cjs/fit_types.d.ts +23 -1
- package/dist/cjs/profile-lookup-data.d.ts +12 -0
- package/dist/cjs/profile-lookup-data.js +990 -0
- package/dist/cjs/profile-lookup.js +8 -13
- package/dist/cjs/profile.js +172 -981
- package/dist/cjs/type_generator.js +1 -0
- package/dist/fit-parser.d.ts +8 -0
- package/dist/fit-parser.js +84 -0
- package/dist/fit_types.d.ts +23 -1
- package/dist/profile-lookup-data.d.ts +12 -0
- package/dist/profile-lookup-data.js +987 -0
- package/dist/profile-lookup.js +1 -6
- package/dist/profile.js +172 -981
- package/dist/type_generator.js +1 -0
- package/package.json +18 -7
|
@@ -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',
|
package/dist/fit-parser.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/fit-parser.js
CHANGED
|
@@ -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;
|
package/dist/fit_types.d.ts
CHANGED
|
@@ -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;
|