fit-file-parser 3.1.0 → 4.0.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 +22 -0
- package/dist/binary.d.ts +9 -1
- package/dist/binary.js +117 -47
- package/dist/cjs/binary.d.ts +9 -1
- package/dist/cjs/binary.js +117 -47
- package/dist/cjs/fit-parser.js +13 -12
- package/dist/cjs/fit.d.ts +2 -1
- package/dist/cjs/fit.js +7 -0
- package/dist/cjs/fit_types.d.ts +2 -0
- package/dist/fit-parser.js +14 -13
- package/dist/fit.d.ts +2 -1
- package/dist/fit.js +7 -0
- package/dist/fit_types.d.ts +2 -0
- package/package.json +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### FIT decoder performance
|
|
6
|
+
|
|
7
|
+
- Parse `ArrayBuffer` inputs and exact Node.js `Buffer` views directly, avoiding a full copy of the source file.
|
|
8
|
+
- Reuse one parse-local `DataView` instead of allocating temporary views for each supported endian field.
|
|
9
|
+
- Cache standard field metadata and enum/mask lookups on reusable message definitions.
|
|
10
|
+
- Reuse raw field storage for records that share a local message definition.
|
|
11
|
+
- Generate elapsed and timer fields once per record instead of once per decoded field.
|
|
12
|
+
- Skip header and file CRC scans in `force: true` mode, where CRC mismatches are intentionally ignored; strict mode continues to validate both CRCs.
|
|
13
|
+
- Preserve legacy malformed-field zero-padding, field-boundary, developer-field, invalid-value, and offset-buffer behavior.
|
|
14
|
+
|
|
15
|
+
### Measured benefit
|
|
16
|
+
|
|
17
|
+
- Suunto 93-hour / 7.5 MB FIT decoding: 1.057 s to 0.375 s, a 64.5% reduction.
|
|
18
|
+
- Garmin 110-hour / 28.6 MB FIT decoding: 3.643 s to 1.241 s, a 65.9% reduction.
|
|
19
|
+
- Input-related array-buffer memory is approximately halved by removing the full source copy:
|
|
20
|
+
- Suunto: 15.0 MB to 7.5 MB.
|
|
21
|
+
- Garmin: 57.2 MB to 28.6 MB.
|
|
22
|
+
|
|
23
|
+
There are no intentional parsed-output or public API changes in this release. Output parity was verified across 162 checked-in fixture/mode combinations, 8,320 generated malformed endian-definition cases, and both private long-duration benchmark files.
|
|
24
|
+
|
|
3
25
|
## 3.1.0
|
|
4
26
|
|
|
5
27
|
- Add the public `FitEncoder` API for writing FIT headers, definitions, data messages, and CRCs.
|
package/dist/binary.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { FitParserOptions } from './fit-parser.js';
|
|
2
|
+
import type { FieldDefinition } from './fit.js';
|
|
2
3
|
import type { MesgNum } from './fit_types.js';
|
|
3
4
|
import { Buffer } from 'buffer';
|
|
5
|
+
export interface MessageTypeDefinition {
|
|
6
|
+
littleEndian: boolean;
|
|
7
|
+
globalMessageNumber: number;
|
|
8
|
+
numberOfFields: number;
|
|
9
|
+
fieldDefs: FieldDefinition[];
|
|
10
|
+
rawData?: any[];
|
|
11
|
+
}
|
|
4
12
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
5
|
-
export declare function readRecord(blob: Uint8Array, messageTypes:
|
|
13
|
+
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView): {
|
|
6
14
|
messageType: MesgNum | '';
|
|
7
15
|
nextIndex: number;
|
|
8
16
|
message?: any;
|
package/dist/binary.js
CHANGED
|
@@ -5,6 +5,29 @@ const CompressedLocalMsgNumMask = 0x60;
|
|
|
5
5
|
const CompressedHeaderMask = 0x80;
|
|
6
6
|
const GarminTimeOffset = 631065600000;
|
|
7
7
|
let monitoring_timestamp = 0;
|
|
8
|
+
const InvalidFieldData = Symbol('invalid FIT field data');
|
|
9
|
+
const formatTypeMetadata = new Map();
|
|
10
|
+
function requiresBoundedEndianDataView(type, size) {
|
|
11
|
+
switch (type) {
|
|
12
|
+
case 'sint16':
|
|
13
|
+
case 'uint16':
|
|
14
|
+
case 'uint16z':
|
|
15
|
+
return size < 2;
|
|
16
|
+
case 'sint32':
|
|
17
|
+
case 'uint32':
|
|
18
|
+
case 'uint32z':
|
|
19
|
+
case 'float32':
|
|
20
|
+
return size < 4;
|
|
21
|
+
case 'float64':
|
|
22
|
+
return size < 8;
|
|
23
|
+
case 'uint16_array':
|
|
24
|
+
return size % 2 !== 0;
|
|
25
|
+
case 'uint32_array':
|
|
26
|
+
return size % 4 !== 0;
|
|
27
|
+
default:
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
8
31
|
export function addEndian(littleEndian, bytes) {
|
|
9
32
|
let result = 0;
|
|
10
33
|
if (!littleEndian)
|
|
@@ -14,7 +37,8 @@ export function addEndian(littleEndian, bytes) {
|
|
|
14
37
|
}
|
|
15
38
|
return result;
|
|
16
39
|
}
|
|
17
|
-
function readData(blob, fDef, startIndex, options) {
|
|
40
|
+
function readData(blob, dataView, fDef, startIndex, options) {
|
|
41
|
+
var _a;
|
|
18
42
|
if (fDef.type === 'uint8_array') {
|
|
19
43
|
const array8 = [];
|
|
20
44
|
for (let i = 0; i < fDef.size; i++) {
|
|
@@ -23,39 +47,50 @@ function readData(blob, fDef, startIndex, options) {
|
|
|
23
47
|
return array8;
|
|
24
48
|
}
|
|
25
49
|
if (fDef.endianAbility) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
const requiresBoundedDataView = (_a = fDef.requiresBoundedDataView) !== null && _a !== void 0 ? _a : (fDef.requiresBoundedDataView = requiresBoundedEndianDataView(fDef.type, fDef.size));
|
|
51
|
+
let fieldDataView = dataView;
|
|
52
|
+
let fieldStartIndex = startIndex;
|
|
53
|
+
if (startIndex < 0
|
|
54
|
+
|| startIndex + fDef.size > blob.length
|
|
55
|
+
|| startIndex + fDef.size > dataView.byteLength) {
|
|
56
|
+
const paddedField = new Uint8Array(fDef.size);
|
|
57
|
+
for (let index = 0; index < fDef.size; index++) {
|
|
58
|
+
paddedField[index] = blob[startIndex + index];
|
|
59
|
+
}
|
|
60
|
+
fieldDataView = new DataView(paddedField.buffer);
|
|
61
|
+
fieldStartIndex = 0;
|
|
62
|
+
}
|
|
63
|
+
else if (requiresBoundedDataView) {
|
|
64
|
+
fieldDataView = new DataView(dataView.buffer, dataView.byteOffset + startIndex, fDef.size);
|
|
65
|
+
fieldStartIndex = 0;
|
|
29
66
|
}
|
|
30
|
-
const { buffer } = new Uint8Array(temp);
|
|
31
|
-
const dataView = new DataView(buffer);
|
|
32
67
|
try {
|
|
33
68
|
switch (fDef.type) {
|
|
34
69
|
case 'sint16':
|
|
35
|
-
return
|
|
70
|
+
return fieldDataView.getInt16(fieldStartIndex, fDef.littleEndian);
|
|
36
71
|
case 'uint16':
|
|
37
72
|
case 'uint16z':
|
|
38
|
-
return
|
|
73
|
+
return fieldDataView.getUint16(fieldStartIndex, fDef.littleEndian);
|
|
39
74
|
case 'sint32':
|
|
40
|
-
return
|
|
75
|
+
return fieldDataView.getInt32(fieldStartIndex, fDef.littleEndian);
|
|
41
76
|
case 'uint32':
|
|
42
77
|
case 'uint32z':
|
|
43
|
-
return
|
|
78
|
+
return fieldDataView.getUint32(fieldStartIndex, fDef.littleEndian);
|
|
44
79
|
case 'float32':
|
|
45
|
-
return
|
|
80
|
+
return fieldDataView.getFloat32(fieldStartIndex, fDef.littleEndian);
|
|
46
81
|
case 'float64':
|
|
47
|
-
return
|
|
82
|
+
return fieldDataView.getFloat64(fieldStartIndex, fDef.littleEndian);
|
|
48
83
|
case 'uint32_array': {
|
|
49
84
|
const array32 = [];
|
|
50
85
|
for (let i = 0; i < fDef.size; i += 4) {
|
|
51
|
-
array32.push(
|
|
86
|
+
array32.push(fieldDataView.getUint32(fieldStartIndex + i, fDef.littleEndian));
|
|
52
87
|
}
|
|
53
88
|
return array32;
|
|
54
89
|
}
|
|
55
90
|
case 'uint16_array': {
|
|
56
91
|
const array16 = [];
|
|
57
92
|
for (let i = 0; i < fDef.size; i += 2) {
|
|
58
|
-
array16.push(
|
|
93
|
+
array16.push(fieldDataView.getUint16(fieldStartIndex + i, fDef.littleEndian));
|
|
59
94
|
}
|
|
60
95
|
return array16;
|
|
61
96
|
}
|
|
@@ -66,6 +101,10 @@ function readData(blob, fDef, startIndex, options) {
|
|
|
66
101
|
throw e;
|
|
67
102
|
}
|
|
68
103
|
}
|
|
104
|
+
const temp = [];
|
|
105
|
+
for (let i = 0; i < fDef.size; i++) {
|
|
106
|
+
temp.push(blob[startIndex + i]);
|
|
107
|
+
}
|
|
69
108
|
return addEndian(fDef.littleEndian, temp);
|
|
70
109
|
}
|
|
71
110
|
if (fDef.type === 'string') {
|
|
@@ -117,30 +156,37 @@ function formatByType(data, type, scale, offset) {
|
|
|
117
156
|
return scale ? data / scale + offset : data;
|
|
118
157
|
default:
|
|
119
158
|
{
|
|
120
|
-
|
|
159
|
+
const typeMap = FIT.types[type];
|
|
160
|
+
if (!typeMap) {
|
|
121
161
|
return data;
|
|
122
162
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
let metadata = formatTypeMetadata.get(type);
|
|
164
|
+
if (!metadata) {
|
|
165
|
+
const entries = [];
|
|
166
|
+
let hasMask = false;
|
|
167
|
+
for (const key in typeMap) {
|
|
168
|
+
if (key in typeMap) {
|
|
169
|
+
const value = typeMap[key];
|
|
170
|
+
entries.push([key, value]);
|
|
171
|
+
if (String(value) === 'mask') {
|
|
172
|
+
hasMask = true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
128
175
|
}
|
|
176
|
+
metadata = { entries, hasMask, typeMap };
|
|
177
|
+
formatTypeMetadata.set(type, metadata);
|
|
129
178
|
}
|
|
130
|
-
if (!
|
|
131
|
-
const
|
|
132
|
-
const mapped = typeMap[String(data)];
|
|
179
|
+
if (!metadata.hasMask) {
|
|
180
|
+
const mapped = metadata.typeMap[String(data)];
|
|
133
181
|
return mapped === undefined ? data : mapped;
|
|
134
182
|
}
|
|
135
183
|
const dataItem = {};
|
|
136
|
-
for (const key
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
dataItem[FIT.types[type][key]] = !!((data & Number(key)) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
143
|
-
}
|
|
184
|
+
for (const [key, value] of metadata.entries) {
|
|
185
|
+
if (value === 'mask') {
|
|
186
|
+
dataItem.value = data & Number(key);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
dataItem[value] = !!((data & Number(key)) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
144
190
|
}
|
|
145
191
|
}
|
|
146
192
|
return dataItem;
|
|
@@ -254,6 +300,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
254
300
|
case 'gps_accuracy':
|
|
255
301
|
return convertTo(data, 'lengthUnits', options.lengthUnit);
|
|
256
302
|
case 'temperature':
|
|
303
|
+
case 'min_temperature':
|
|
257
304
|
case 'avg_temperature':
|
|
258
305
|
case 'max_temperature':
|
|
259
306
|
return convertTo(data, 'temperatureUnits', options.temperatureUnit);
|
|
@@ -272,8 +319,8 @@ function applyOptions(data, field, options, fields) {
|
|
|
272
319
|
return data;
|
|
273
320
|
}
|
|
274
321
|
}
|
|
275
|
-
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
|
|
276
|
-
var _a, _b;
|
|
322
|
+
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength)) {
|
|
323
|
+
var _a, _b, _c, _d, _e;
|
|
277
324
|
const recordHeader = blob[startIndex];
|
|
278
325
|
let localMessageType = recordHeader & 15;
|
|
279
326
|
if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
|
|
@@ -297,12 +344,13 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
297
344
|
]),
|
|
298
345
|
numberOfFields: numberOfFields + numberOfDeveloperDataFields,
|
|
299
346
|
fieldDefs: [],
|
|
347
|
+
rawData: [],
|
|
300
348
|
};
|
|
301
349
|
const message = getFitMessage(mTypeDef.globalMessageNumber);
|
|
302
350
|
for (let i = 0; i < numberOfFields; i++) {
|
|
303
351
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
304
352
|
const baseType = blob[fDefIndex + 2];
|
|
305
|
-
const { field, type } = message.getAttributes(blob[fDefIndex]);
|
|
353
|
+
const { field, type, scale, offset } = message.getAttributes(blob[fDefIndex]);
|
|
306
354
|
const fDef = {
|
|
307
355
|
type,
|
|
308
356
|
fDefNo: blob[fDefIndex],
|
|
@@ -312,6 +360,9 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
312
360
|
baseTypeNo: baseType,
|
|
313
361
|
name: field,
|
|
314
362
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
363
|
+
scale,
|
|
364
|
+
offset,
|
|
365
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(type, blob[fDefIndex + 1]),
|
|
315
366
|
};
|
|
316
367
|
mTypeDef.fieldDefs.push(fDef);
|
|
317
368
|
}
|
|
@@ -336,6 +387,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
336
387
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
337
388
|
scale: devDef.scale || 1,
|
|
338
389
|
offset: devDef.offset || 0,
|
|
390
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(FIT.types.fit_base_type[baseType], size),
|
|
339
391
|
developerDataIndex: devDataIndex,
|
|
340
392
|
isDeveloperField: true,
|
|
341
393
|
};
|
|
@@ -348,6 +400,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
348
400
|
throw e;
|
|
349
401
|
}
|
|
350
402
|
}
|
|
403
|
+
mTypeDef.rawData = Array.from({ length: mTypeDef.fieldDefs.length }, () => InvalidFieldData);
|
|
351
404
|
messageTypes[localMessageType] = mTypeDef;
|
|
352
405
|
const nextIndex = startIndex + 6 + mTypeDef.numberOfFields * 3;
|
|
353
406
|
const nextIndexWithDeveloperData = nextIndex + 1;
|
|
@@ -363,40 +416,57 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
363
416
|
let readDataFromIndex = startIndex + 1;
|
|
364
417
|
const fields = {};
|
|
365
418
|
const message = getFitMessage(messageType.globalMessageNumber);
|
|
366
|
-
const
|
|
419
|
+
const rawData = (_a = messageType.rawData) !== null && _a !== void 0 ? _a : (messageType.rawData = Array.from({ length: messageType.fieldDefs.length }, () => InvalidFieldData));
|
|
420
|
+
let validFieldCount = 0;
|
|
367
421
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
368
422
|
const fDef = messageType.fieldDefs[i];
|
|
369
|
-
const data = readData(blob, fDef, readDataFromIndex, options);
|
|
423
|
+
const data = readData(blob, dataView, fDef, readDataFromIndex, options);
|
|
370
424
|
if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
371
|
-
|
|
425
|
+
rawData[i] = data;
|
|
426
|
+
validFieldCount++;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
rawData[i] = InvalidFieldData;
|
|
372
430
|
}
|
|
373
431
|
readDataFromIndex += fDef.size;
|
|
374
432
|
messageSize += fDef.size;
|
|
375
433
|
}
|
|
376
|
-
for (
|
|
377
|
-
const
|
|
434
|
+
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
435
|
+
const data = rawData[i];
|
|
436
|
+
if (data === InvalidFieldData) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const fDef = messageType.fieldDefs[i];
|
|
440
|
+
const field = fDef.name;
|
|
378
441
|
if (field !== 'unknown' && field !== '' && field !== undefined) {
|
|
379
442
|
fields[field] = data;
|
|
380
443
|
}
|
|
381
444
|
}
|
|
382
|
-
for (
|
|
445
|
+
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
446
|
+
const data = rawData[i];
|
|
447
|
+
if (data === InvalidFieldData) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const fDef = messageType.fieldDefs[i];
|
|
383
451
|
if (fDef.isDeveloperField) {
|
|
384
452
|
const field = fDef.name;
|
|
385
453
|
const { type } = fDef;
|
|
386
|
-
const scale = (
|
|
387
|
-
const offset = (
|
|
454
|
+
const scale = (_b = fDef.scale) !== null && _b !== void 0 ? _b : null;
|
|
455
|
+
const offset = (_c = fDef.offset) !== null && _c !== void 0 ? _c : 0;
|
|
388
456
|
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
389
457
|
}
|
|
390
458
|
else {
|
|
391
|
-
const { field, type
|
|
459
|
+
const { name: field, type } = fDef;
|
|
460
|
+
const scale = (_d = fDef.scale) !== null && _d !== void 0 ? _d : null;
|
|
461
|
+
const offset = (_e = fDef.offset) !== null && _e !== void 0 ? _e : 0;
|
|
392
462
|
if (field !== 'unknown' && field !== '' && field !== undefined) {
|
|
393
463
|
fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
394
464
|
}
|
|
395
465
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
466
|
+
}
|
|
467
|
+
if (validFieldCount > 0 && message.name === 'record' && options.elapsedRecordField) {
|
|
468
|
+
fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
|
|
469
|
+
fields.timer_time = fields.elapsed_time - pausedTime;
|
|
400
470
|
}
|
|
401
471
|
if (message.name === 'field_description') {
|
|
402
472
|
developerFields[fields.developer_data_index]
|
package/dist/cjs/binary.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { FitParserOptions } from './fit-parser.js';
|
|
2
|
+
import type { FieldDefinition } from './fit.js';
|
|
2
3
|
import type { MesgNum } from './fit_types.js';
|
|
3
4
|
import { Buffer } from 'buffer';
|
|
5
|
+
export interface MessageTypeDefinition {
|
|
6
|
+
littleEndian: boolean;
|
|
7
|
+
globalMessageNumber: number;
|
|
8
|
+
numberOfFields: number;
|
|
9
|
+
fieldDefs: FieldDefinition[];
|
|
10
|
+
rawData?: any[];
|
|
11
|
+
}
|
|
4
12
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
5
|
-
export declare function readRecord(blob: Uint8Array, messageTypes:
|
|
13
|
+
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView): {
|
|
6
14
|
messageType: MesgNum | '';
|
|
7
15
|
nextIndex: number;
|
|
8
16
|
message?: any;
|
package/dist/cjs/binary.js
CHANGED
|
@@ -11,6 +11,29 @@ const CompressedLocalMsgNumMask = 0x60;
|
|
|
11
11
|
const CompressedHeaderMask = 0x80;
|
|
12
12
|
const GarminTimeOffset = 631065600000;
|
|
13
13
|
let monitoring_timestamp = 0;
|
|
14
|
+
const InvalidFieldData = Symbol('invalid FIT field data');
|
|
15
|
+
const formatTypeMetadata = new Map();
|
|
16
|
+
function requiresBoundedEndianDataView(type, size) {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case 'sint16':
|
|
19
|
+
case 'uint16':
|
|
20
|
+
case 'uint16z':
|
|
21
|
+
return size < 2;
|
|
22
|
+
case 'sint32':
|
|
23
|
+
case 'uint32':
|
|
24
|
+
case 'uint32z':
|
|
25
|
+
case 'float32':
|
|
26
|
+
return size < 4;
|
|
27
|
+
case 'float64':
|
|
28
|
+
return size < 8;
|
|
29
|
+
case 'uint16_array':
|
|
30
|
+
return size % 2 !== 0;
|
|
31
|
+
case 'uint32_array':
|
|
32
|
+
return size % 4 !== 0;
|
|
33
|
+
default:
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
14
37
|
function addEndian(littleEndian, bytes) {
|
|
15
38
|
let result = 0;
|
|
16
39
|
if (!littleEndian)
|
|
@@ -20,7 +43,8 @@ function addEndian(littleEndian, bytes) {
|
|
|
20
43
|
}
|
|
21
44
|
return result;
|
|
22
45
|
}
|
|
23
|
-
function readData(blob, fDef, startIndex, options) {
|
|
46
|
+
function readData(blob, dataView, fDef, startIndex, options) {
|
|
47
|
+
var _a;
|
|
24
48
|
if (fDef.type === 'uint8_array') {
|
|
25
49
|
const array8 = [];
|
|
26
50
|
for (let i = 0; i < fDef.size; i++) {
|
|
@@ -29,39 +53,50 @@ function readData(blob, fDef, startIndex, options) {
|
|
|
29
53
|
return array8;
|
|
30
54
|
}
|
|
31
55
|
if (fDef.endianAbility) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
const requiresBoundedDataView = (_a = fDef.requiresBoundedDataView) !== null && _a !== void 0 ? _a : (fDef.requiresBoundedDataView = requiresBoundedEndianDataView(fDef.type, fDef.size));
|
|
57
|
+
let fieldDataView = dataView;
|
|
58
|
+
let fieldStartIndex = startIndex;
|
|
59
|
+
if (startIndex < 0
|
|
60
|
+
|| startIndex + fDef.size > blob.length
|
|
61
|
+
|| startIndex + fDef.size > dataView.byteLength) {
|
|
62
|
+
const paddedField = new Uint8Array(fDef.size);
|
|
63
|
+
for (let index = 0; index < fDef.size; index++) {
|
|
64
|
+
paddedField[index] = blob[startIndex + index];
|
|
65
|
+
}
|
|
66
|
+
fieldDataView = new DataView(paddedField.buffer);
|
|
67
|
+
fieldStartIndex = 0;
|
|
68
|
+
}
|
|
69
|
+
else if (requiresBoundedDataView) {
|
|
70
|
+
fieldDataView = new DataView(dataView.buffer, dataView.byteOffset + startIndex, fDef.size);
|
|
71
|
+
fieldStartIndex = 0;
|
|
35
72
|
}
|
|
36
|
-
const { buffer } = new Uint8Array(temp);
|
|
37
|
-
const dataView = new DataView(buffer);
|
|
38
73
|
try {
|
|
39
74
|
switch (fDef.type) {
|
|
40
75
|
case 'sint16':
|
|
41
|
-
return
|
|
76
|
+
return fieldDataView.getInt16(fieldStartIndex, fDef.littleEndian);
|
|
42
77
|
case 'uint16':
|
|
43
78
|
case 'uint16z':
|
|
44
|
-
return
|
|
79
|
+
return fieldDataView.getUint16(fieldStartIndex, fDef.littleEndian);
|
|
45
80
|
case 'sint32':
|
|
46
|
-
return
|
|
81
|
+
return fieldDataView.getInt32(fieldStartIndex, fDef.littleEndian);
|
|
47
82
|
case 'uint32':
|
|
48
83
|
case 'uint32z':
|
|
49
|
-
return
|
|
84
|
+
return fieldDataView.getUint32(fieldStartIndex, fDef.littleEndian);
|
|
50
85
|
case 'float32':
|
|
51
|
-
return
|
|
86
|
+
return fieldDataView.getFloat32(fieldStartIndex, fDef.littleEndian);
|
|
52
87
|
case 'float64':
|
|
53
|
-
return
|
|
88
|
+
return fieldDataView.getFloat64(fieldStartIndex, fDef.littleEndian);
|
|
54
89
|
case 'uint32_array': {
|
|
55
90
|
const array32 = [];
|
|
56
91
|
for (let i = 0; i < fDef.size; i += 4) {
|
|
57
|
-
array32.push(
|
|
92
|
+
array32.push(fieldDataView.getUint32(fieldStartIndex + i, fDef.littleEndian));
|
|
58
93
|
}
|
|
59
94
|
return array32;
|
|
60
95
|
}
|
|
61
96
|
case 'uint16_array': {
|
|
62
97
|
const array16 = [];
|
|
63
98
|
for (let i = 0; i < fDef.size; i += 2) {
|
|
64
|
-
array16.push(
|
|
99
|
+
array16.push(fieldDataView.getUint16(fieldStartIndex + i, fDef.littleEndian));
|
|
65
100
|
}
|
|
66
101
|
return array16;
|
|
67
102
|
}
|
|
@@ -72,6 +107,10 @@ function readData(blob, fDef, startIndex, options) {
|
|
|
72
107
|
throw e;
|
|
73
108
|
}
|
|
74
109
|
}
|
|
110
|
+
const temp = [];
|
|
111
|
+
for (let i = 0; i < fDef.size; i++) {
|
|
112
|
+
temp.push(blob[startIndex + i]);
|
|
113
|
+
}
|
|
75
114
|
return addEndian(fDef.littleEndian, temp);
|
|
76
115
|
}
|
|
77
116
|
if (fDef.type === 'string') {
|
|
@@ -123,30 +162,37 @@ function formatByType(data, type, scale, offset) {
|
|
|
123
162
|
return scale ? data / scale + offset : data;
|
|
124
163
|
default:
|
|
125
164
|
{
|
|
126
|
-
|
|
165
|
+
const typeMap = fit_js_1.FIT.types[type];
|
|
166
|
+
if (!typeMap) {
|
|
127
167
|
return data;
|
|
128
168
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
169
|
+
let metadata = formatTypeMetadata.get(type);
|
|
170
|
+
if (!metadata) {
|
|
171
|
+
const entries = [];
|
|
172
|
+
let hasMask = false;
|
|
173
|
+
for (const key in typeMap) {
|
|
174
|
+
if (key in typeMap) {
|
|
175
|
+
const value = typeMap[key];
|
|
176
|
+
entries.push([key, value]);
|
|
177
|
+
if (String(value) === 'mask') {
|
|
178
|
+
hasMask = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
134
181
|
}
|
|
182
|
+
metadata = { entries, hasMask, typeMap };
|
|
183
|
+
formatTypeMetadata.set(type, metadata);
|
|
135
184
|
}
|
|
136
|
-
if (!
|
|
137
|
-
const
|
|
138
|
-
const mapped = typeMap[String(data)];
|
|
185
|
+
if (!metadata.hasMask) {
|
|
186
|
+
const mapped = metadata.typeMap[String(data)];
|
|
139
187
|
return mapped === undefined ? data : mapped;
|
|
140
188
|
}
|
|
141
189
|
const dataItem = {};
|
|
142
|
-
for (const key
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
dataItem[fit_js_1.FIT.types[type][key]] = !!((data & Number(key)) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
149
|
-
}
|
|
190
|
+
for (const [key, value] of metadata.entries) {
|
|
191
|
+
if (value === 'mask') {
|
|
192
|
+
dataItem.value = data & Number(key);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
dataItem[value] = !!((data & Number(key)) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
150
196
|
}
|
|
151
197
|
}
|
|
152
198
|
return dataItem;
|
|
@@ -260,6 +306,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
260
306
|
case 'gps_accuracy':
|
|
261
307
|
return convertTo(data, 'lengthUnits', options.lengthUnit);
|
|
262
308
|
case 'temperature':
|
|
309
|
+
case 'min_temperature':
|
|
263
310
|
case 'avg_temperature':
|
|
264
311
|
case 'max_temperature':
|
|
265
312
|
return convertTo(data, 'temperatureUnits', options.temperatureUnit);
|
|
@@ -278,8 +325,8 @@ function applyOptions(data, field, options, fields) {
|
|
|
278
325
|
return data;
|
|
279
326
|
}
|
|
280
327
|
}
|
|
281
|
-
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
|
|
282
|
-
var _a, _b;
|
|
328
|
+
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength)) {
|
|
329
|
+
var _a, _b, _c, _d, _e;
|
|
283
330
|
const recordHeader = blob[startIndex];
|
|
284
331
|
let localMessageType = recordHeader & 15;
|
|
285
332
|
if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
|
|
@@ -303,12 +350,13 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
303
350
|
]),
|
|
304
351
|
numberOfFields: numberOfFields + numberOfDeveloperDataFields,
|
|
305
352
|
fieldDefs: [],
|
|
353
|
+
rawData: [],
|
|
306
354
|
};
|
|
307
355
|
const message = (0, messages_js_1.getFitMessage)(mTypeDef.globalMessageNumber);
|
|
308
356
|
for (let i = 0; i < numberOfFields; i++) {
|
|
309
357
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
310
358
|
const baseType = blob[fDefIndex + 2];
|
|
311
|
-
const { field, type } = message.getAttributes(blob[fDefIndex]);
|
|
359
|
+
const { field, type, scale, offset } = message.getAttributes(blob[fDefIndex]);
|
|
312
360
|
const fDef = {
|
|
313
361
|
type,
|
|
314
362
|
fDefNo: blob[fDefIndex],
|
|
@@ -318,6 +366,9 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
318
366
|
baseTypeNo: baseType,
|
|
319
367
|
name: field,
|
|
320
368
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
369
|
+
scale,
|
|
370
|
+
offset,
|
|
371
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(type, blob[fDefIndex + 1]),
|
|
321
372
|
};
|
|
322
373
|
mTypeDef.fieldDefs.push(fDef);
|
|
323
374
|
}
|
|
@@ -342,6 +393,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
342
393
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
343
394
|
scale: devDef.scale || 1,
|
|
344
395
|
offset: devDef.offset || 0,
|
|
396
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(fit_js_1.FIT.types.fit_base_type[baseType], size),
|
|
345
397
|
developerDataIndex: devDataIndex,
|
|
346
398
|
isDeveloperField: true,
|
|
347
399
|
};
|
|
@@ -354,6 +406,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
354
406
|
throw e;
|
|
355
407
|
}
|
|
356
408
|
}
|
|
409
|
+
mTypeDef.rawData = Array.from({ length: mTypeDef.fieldDefs.length }, () => InvalidFieldData);
|
|
357
410
|
messageTypes[localMessageType] = mTypeDef;
|
|
358
411
|
const nextIndex = startIndex + 6 + mTypeDef.numberOfFields * 3;
|
|
359
412
|
const nextIndexWithDeveloperData = nextIndex + 1;
|
|
@@ -369,40 +422,57 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
369
422
|
let readDataFromIndex = startIndex + 1;
|
|
370
423
|
const fields = {};
|
|
371
424
|
const message = (0, messages_js_1.getFitMessage)(messageType.globalMessageNumber);
|
|
372
|
-
const
|
|
425
|
+
const rawData = (_a = messageType.rawData) !== null && _a !== void 0 ? _a : (messageType.rawData = Array.from({ length: messageType.fieldDefs.length }, () => InvalidFieldData));
|
|
426
|
+
let validFieldCount = 0;
|
|
373
427
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
374
428
|
const fDef = messageType.fieldDefs[i];
|
|
375
|
-
const data = readData(blob, fDef, readDataFromIndex, options);
|
|
429
|
+
const data = readData(blob, dataView, fDef, readDataFromIndex, options);
|
|
376
430
|
if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
377
|
-
|
|
431
|
+
rawData[i] = data;
|
|
432
|
+
validFieldCount++;
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
rawData[i] = InvalidFieldData;
|
|
378
436
|
}
|
|
379
437
|
readDataFromIndex += fDef.size;
|
|
380
438
|
messageSize += fDef.size;
|
|
381
439
|
}
|
|
382
|
-
for (
|
|
383
|
-
const
|
|
440
|
+
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
441
|
+
const data = rawData[i];
|
|
442
|
+
if (data === InvalidFieldData) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const fDef = messageType.fieldDefs[i];
|
|
446
|
+
const field = fDef.name;
|
|
384
447
|
if (field !== 'unknown' && field !== '' && field !== undefined) {
|
|
385
448
|
fields[field] = data;
|
|
386
449
|
}
|
|
387
450
|
}
|
|
388
|
-
for (
|
|
451
|
+
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
452
|
+
const data = rawData[i];
|
|
453
|
+
if (data === InvalidFieldData) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const fDef = messageType.fieldDefs[i];
|
|
389
457
|
if (fDef.isDeveloperField) {
|
|
390
458
|
const field = fDef.name;
|
|
391
459
|
const { type } = fDef;
|
|
392
|
-
const scale = (
|
|
393
|
-
const offset = (
|
|
460
|
+
const scale = (_b = fDef.scale) !== null && _b !== void 0 ? _b : null;
|
|
461
|
+
const offset = (_c = fDef.offset) !== null && _c !== void 0 ? _c : 0;
|
|
394
462
|
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
395
463
|
}
|
|
396
464
|
else {
|
|
397
|
-
const { field, type
|
|
465
|
+
const { name: field, type } = fDef;
|
|
466
|
+
const scale = (_d = fDef.scale) !== null && _d !== void 0 ? _d : null;
|
|
467
|
+
const offset = (_e = fDef.offset) !== null && _e !== void 0 ? _e : 0;
|
|
398
468
|
if (field !== 'unknown' && field !== '' && field !== undefined) {
|
|
399
469
|
fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
400
470
|
}
|
|
401
471
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
472
|
+
}
|
|
473
|
+
if (validFieldCount > 0 && message.name === 'record' && options.elapsedRecordField) {
|
|
474
|
+
fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
|
|
475
|
+
fields.timer_time = fields.elapsed_time - pausedTime;
|
|
406
476
|
}
|
|
407
477
|
if (message.name === 'field_description') {
|
|
408
478
|
developerFields[fields.developer_data_index]
|
package/dist/cjs/fit-parser.js
CHANGED
|
@@ -32,7 +32,10 @@ class FitParser {
|
|
|
32
32
|
}
|
|
33
33
|
parse(content, callback) {
|
|
34
34
|
var _a;
|
|
35
|
-
const blob =
|
|
35
|
+
const blob = content instanceof ArrayBuffer
|
|
36
|
+
? new Uint8Array(content)
|
|
37
|
+
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
38
|
+
const dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
|
|
36
39
|
if (blob.length < 12) {
|
|
37
40
|
callback('File to small to be a FIT file', undefined);
|
|
38
41
|
if (!this.options.force) {
|
|
@@ -56,27 +59,25 @@ class FitParser {
|
|
|
56
59
|
return;
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
if (headerLength === 14) {
|
|
62
|
+
if (headerLength === 14 && !this.options.force) {
|
|
60
63
|
const crcHeader = blob[12] + (blob[13] << 8);
|
|
61
64
|
const crcHeaderCalc = (0, binary_js_1.calculateCRC)(blob, 0, 12);
|
|
62
65
|
if (crcHeader !== crcHeaderCalc) {
|
|
63
66
|
// callback('Header CRC mismatch', {});
|
|
64
67
|
// TODO: fix Header CRC check
|
|
65
|
-
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
+
return;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
const protocolVersion = blob[1];
|
|
71
72
|
const profileVersion = blob[2] + (blob[3] << 8);
|
|
72
73
|
const dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);
|
|
73
74
|
const crcStart = dataLength + headerLength;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
if (!this.options.force) {
|
|
76
|
+
const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
|
|
77
|
+
const crcFileCalc = (0, binary_js_1.calculateCRC)(blob, headerLength === 12 ? 0 : headerLength, crcStart);
|
|
78
|
+
if (crcFile !== crcFileCalc) {
|
|
79
|
+
// callback('File CRC mismatch', {});
|
|
80
|
+
// TODO: fix File CRC check
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -120,7 +121,7 @@ class FitParser {
|
|
|
120
121
|
let lastStopTimestamp;
|
|
121
122
|
let pausedTime = 0;
|
|
122
123
|
while (loopIndex < crcStart) {
|
|
123
|
-
const { nextIndex, messageType, message } = (0, binary_js_1.readRecord)(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime);
|
|
124
|
+
const { nextIndex, messageType, message } = (0, binary_js_1.readRecord)(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView);
|
|
124
125
|
loopIndex = nextIndex;
|
|
125
126
|
switch (messageType) {
|
|
126
127
|
case 'lap':
|
package/dist/cjs/fit.d.ts
CHANGED
|
@@ -8,8 +8,9 @@ export interface FieldDefinition {
|
|
|
8
8
|
baseTypeNo: number;
|
|
9
9
|
name: string;
|
|
10
10
|
dataType: string;
|
|
11
|
-
scale?: number;
|
|
11
|
+
scale?: number | null;
|
|
12
12
|
offset?: number;
|
|
13
|
+
requiresBoundedDataView?: boolean;
|
|
13
14
|
developerDataIndex?: number;
|
|
14
15
|
isDeveloperField?: boolean;
|
|
15
16
|
}
|
package/dist/cjs/fit.js
CHANGED
|
@@ -2554,6 +2554,13 @@ exports.FIT = {
|
|
|
2554
2554
|
},
|
|
2555
2555
|
20: {
|
|
2556
2556
|
name: 'record',
|
|
2557
|
+
90: {
|
|
2558
|
+
field: 'garmin_performance_condition',
|
|
2559
|
+
type: 'sint8',
|
|
2560
|
+
scale: null,
|
|
2561
|
+
offset: 0,
|
|
2562
|
+
units: '',
|
|
2563
|
+
},
|
|
2557
2564
|
253: {
|
|
2558
2565
|
field: 'timestamp',
|
|
2559
2566
|
type: 'date_time',
|
package/dist/cjs/fit_types.d.ts
CHANGED
|
@@ -642,6 +642,7 @@ export interface ParsedRecord {
|
|
|
642
642
|
vertical_ratio?: number;
|
|
643
643
|
stance_time_balance?: number;
|
|
644
644
|
step_length?: number;
|
|
645
|
+
garmin_performance_condition?: number;
|
|
645
646
|
absolute_pressure?: number;
|
|
646
647
|
depth?: number;
|
|
647
648
|
next_stop_depth?: number;
|
|
@@ -729,6 +730,7 @@ export interface ParsedCourse {
|
|
|
729
730
|
sport?: Sport;
|
|
730
731
|
name?: string;
|
|
731
732
|
capabilities?: CourseCapabilities;
|
|
733
|
+
sub_sport?: SubSport;
|
|
732
734
|
}
|
|
733
735
|
export interface ParsedCoursePoint {
|
|
734
736
|
timestamp: string;
|
package/dist/fit-parser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { calculateCRC,
|
|
1
|
+
import { calculateCRC, readRecord } from './binary.js';
|
|
2
2
|
import { mapDataIntoLap, mapDataIntoSession } from './helper.js';
|
|
3
3
|
export { FitBaseType, FitEncoder } from './fit-encoder.js';
|
|
4
4
|
export default class FitParser {
|
|
@@ -27,7 +27,10 @@ export default class FitParser {
|
|
|
27
27
|
}
|
|
28
28
|
parse(content, callback) {
|
|
29
29
|
var _a;
|
|
30
|
-
const blob =
|
|
30
|
+
const blob = content instanceof ArrayBuffer
|
|
31
|
+
? new Uint8Array(content)
|
|
32
|
+
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
33
|
+
const dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
|
|
31
34
|
if (blob.length < 12) {
|
|
32
35
|
callback('File to small to be a FIT file', undefined);
|
|
33
36
|
if (!this.options.force) {
|
|
@@ -51,27 +54,25 @@ export default class FitParser {
|
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
|
-
if (headerLength === 14) {
|
|
57
|
+
if (headerLength === 14 && !this.options.force) {
|
|
55
58
|
const crcHeader = blob[12] + (blob[13] << 8);
|
|
56
59
|
const crcHeaderCalc = calculateCRC(blob, 0, 12);
|
|
57
60
|
if (crcHeader !== crcHeaderCalc) {
|
|
58
61
|
// callback('Header CRC mismatch', {});
|
|
59
62
|
// TODO: fix Header CRC check
|
|
60
|
-
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
+
return;
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
const protocolVersion = blob[1];
|
|
66
67
|
const profileVersion = blob[2] + (blob[3] << 8);
|
|
67
68
|
const dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);
|
|
68
69
|
const crcStart = dataLength + headerLength;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
if (!this.options.force) {
|
|
71
|
+
const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
|
|
72
|
+
const crcFileCalc = calculateCRC(blob, headerLength === 12 ? 0 : headerLength, crcStart);
|
|
73
|
+
if (crcFile !== crcFileCalc) {
|
|
74
|
+
// callback('File CRC mismatch', {});
|
|
75
|
+
// TODO: fix File CRC check
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -115,7 +116,7 @@ export default class FitParser {
|
|
|
115
116
|
let lastStopTimestamp;
|
|
116
117
|
let pausedTime = 0;
|
|
117
118
|
while (loopIndex < crcStart) {
|
|
118
|
-
const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime);
|
|
119
|
+
const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView);
|
|
119
120
|
loopIndex = nextIndex;
|
|
120
121
|
switch (messageType) {
|
|
121
122
|
case 'lap':
|
package/dist/fit.d.ts
CHANGED
|
@@ -8,8 +8,9 @@ export interface FieldDefinition {
|
|
|
8
8
|
baseTypeNo: number;
|
|
9
9
|
name: string;
|
|
10
10
|
dataType: string;
|
|
11
|
-
scale?: number;
|
|
11
|
+
scale?: number | null;
|
|
12
12
|
offset?: number;
|
|
13
|
+
requiresBoundedDataView?: boolean;
|
|
13
14
|
developerDataIndex?: number;
|
|
14
15
|
isDeveloperField?: boolean;
|
|
15
16
|
}
|
package/dist/fit.js
CHANGED
|
@@ -2551,6 +2551,13 @@ export const FIT = {
|
|
|
2551
2551
|
},
|
|
2552
2552
|
20: {
|
|
2553
2553
|
name: 'record',
|
|
2554
|
+
90: {
|
|
2555
|
+
field: 'garmin_performance_condition',
|
|
2556
|
+
type: 'sint8',
|
|
2557
|
+
scale: null,
|
|
2558
|
+
offset: 0,
|
|
2559
|
+
units: '',
|
|
2560
|
+
},
|
|
2554
2561
|
253: {
|
|
2555
2562
|
field: 'timestamp',
|
|
2556
2563
|
type: 'date_time',
|
package/dist/fit_types.d.ts
CHANGED
|
@@ -642,6 +642,7 @@ export interface ParsedRecord {
|
|
|
642
642
|
vertical_ratio?: number;
|
|
643
643
|
stance_time_balance?: number;
|
|
644
644
|
step_length?: number;
|
|
645
|
+
garmin_performance_condition?: number;
|
|
645
646
|
absolute_pressure?: number;
|
|
646
647
|
depth?: number;
|
|
647
648
|
next_stop_depth?: number;
|
|
@@ -729,6 +730,7 @@ export interface ParsedCourse {
|
|
|
729
730
|
sport?: Sport;
|
|
730
731
|
name?: string;
|
|
731
732
|
capabilities?: CourseCapabilities;
|
|
733
|
+
sub_sport?: SubSport;
|
|
732
734
|
}
|
|
733
735
|
export interface ParsedCoursePoint {
|
|
734
736
|
timestamp: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fit-file-parser",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)",
|
|
7
7
|
"author": {
|
|
@@ -32,13 +32,6 @@
|
|
|
32
32
|
"garmin",
|
|
33
33
|
"parse"
|
|
34
34
|
],
|
|
35
|
-
"files": [
|
|
36
|
-
"dist/",
|
|
37
|
-
"README.md",
|
|
38
|
-
"LICENSE",
|
|
39
|
-
"CHANGELOG.md",
|
|
40
|
-
"CONTRIBUTORS.md"
|
|
41
|
-
],
|
|
42
35
|
"exports": {
|
|
43
36
|
".": {
|
|
44
37
|
"types": "./dist/fit-parser.d.ts",
|
|
@@ -47,6 +40,13 @@
|
|
|
47
40
|
}
|
|
48
41
|
},
|
|
49
42
|
"main": "dist/cjs/fit-parser.js",
|
|
43
|
+
"files": [
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"CONTRIBUTORS.md",
|
|
46
|
+
"LICENSE",
|
|
47
|
+
"README.md",
|
|
48
|
+
"dist/"
|
|
49
|
+
],
|
|
50
50
|
"maintainers": [
|
|
51
51
|
{
|
|
52
52
|
"email": "jimmykane9@gmail.com",
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
"lint": "eslint .",
|
|
67
67
|
"fmt": "eslint --fix .",
|
|
68
68
|
"type-check": "tsc --noEmit",
|
|
69
|
-
"build": "tsc --noCheck && tsc --project tsconfig.cjs.json --noCheck && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json"
|
|
69
|
+
"build": "tsc --noCheck && tsc --project tsconfig.cjs.json --noCheck && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json",
|
|
70
|
+
"prepack": "npm run build"
|
|
70
71
|
},
|
|
71
72
|
"test": "vitest",
|
|
72
73
|
"dependencies": {
|