@sports-alliance/sports-lib 6.1.12 → 6.1.14

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.
@@ -41,7 +41,8 @@ class Activity extends duration_class_abstract_1.DurationClassAbstract {
41
41
  this.name = name;
42
42
  }
43
43
  createStream(type) {
44
- return new stream_1.Stream(type, Array(activity_utilities_1.ActivityUtilities.getDataLength(this.startDate, this.endDate)).fill(null));
44
+ const stream = new stream_1.Stream(type, Array(activity_utilities_1.ActivityUtilities.getDataLength(this.startDate, this.endDate)).fill(null));
45
+ return stream;
45
46
  }
46
47
  addDataToStream(type, date, value) {
47
48
  this.getStreamData(type)[this.getDateIndex(date)] = value;
@@ -49,7 +50,7 @@ class Activity extends duration_class_abstract_1.DurationClassAbstract {
49
50
  }
50
51
  addStream(stream) {
51
52
  if (this.streams.find(activityStream => activityStream.type === stream.type)) {
52
- throw new Error(`Duplicate type of stream when adding ${stream.type} to activity ${this.getID()}`);
53
+ throw new Error(`Duplicate type of stream when adding ${stream.type} to activity ${this.getID()} `);
53
54
  }
54
55
  this.streams.push(stream);
55
56
  return this;
@@ -101,7 +102,7 @@ class Activity extends duration_class_abstract_1.DurationClassAbstract {
101
102
  getStream(streamType) {
102
103
  const find = this.streams.find(stream => stream.type === streamType);
103
104
  if (!find) {
104
- throw Error(`No stream found with type ${streamType}`);
105
+ throw Error(`No stream found with type ${streamType} `);
105
106
  }
106
107
  return find;
107
108
  }
@@ -13,6 +13,7 @@ export declare class EventImporterFIT {
13
13
  * Generate streams samples based on lengths on an activity
14
14
  * When based on lengths, an activity do not provides sample under records object
15
15
  * @param sessionObject
16
+ * @param options
16
17
  * @private
17
18
  */
18
19
  private static generateSamplesFromLengths;
@@ -272,7 +272,7 @@ class EventImporterFIT {
272
272
  // Note: this is how Strava generate streams for this kind of activities
273
273
  const isLengthsBased = this.isLengthsBased(sessionObject);
274
274
  const samples = isLengthsBased
275
- ? this.generateSamplesFromLengths(sessionObject)
275
+ ? this.generateSamplesFromLengths(sessionObject, options)
276
276
  : fitDataObject.records.filter((record) => {
277
277
  return record.timestamp >= activity.startDate && record.timestamp <= activity.endDate;
278
278
  });
@@ -351,9 +351,10 @@ class EventImporterFIT {
351
351
  * Generate streams samples based on lengths on an activity
352
352
  * When based on lengths, an activity do not provides sample under records object
353
353
  * @param sessionObject
354
+ * @param options
354
355
  * @private
355
356
  */
356
- static generateSamplesFromLengths(sessionObject) {
357
+ static generateSamplesFromLengths(sessionObject, options) {
357
358
  if (!this.isLengthsBased(sessionObject)) {
358
359
  throw new parsing_event_lib_error_1.ParsingEventLibError('Trying to get samples from activities lengths, but no lengths is available');
359
360
  }
@@ -370,12 +371,18 @@ class EventImporterFIT {
370
371
  lap.lengths.forEach((length) => {
371
372
  // Resolve start/end date of current length
372
373
  const lengthStartDate = length.start_time;
373
- const lengthEndDate = new Date(lengthStartDate.getTime() + (length.total_timer_time || length.total_elapsed_time || 0) * 1000);
374
+ const lengthDuration = (length.total_timer_time || length.total_elapsed_time || 0);
375
+ const lengthEndDate = new Date(lengthStartDate.getTime() + lengthDuration * 1000);
376
+ // We check if length is valid comparing to max activity duration
377
+ if (lengthDuration > options.maxActivityDurationDays * 24 * 60 * 60) {
378
+ return;
379
+ }
374
380
  if (lengthEndDate.getTime() <= lengthStartDate.getTime()) {
375
381
  return;
376
382
  }
377
383
  // Generate a stream from length start date to end date filled by null values
378
- let lengthStream = Array(activity_utilities_1.ActivityUtilities.getDataLength(lengthStartDate, lengthEndDate)).fill(null);
384
+ const streamLength = activity_utilities_1.ActivityUtilities.getDataLength(lengthStartDate, lengthEndDate);
385
+ let lengthStream = Array(streamLength).fill(null);
379
386
  // Define distance step to be used for distance stream
380
387
  const lengthStepSize = lengthMeters / (lengthStream.length - 1);
381
388
  // Generate the length stream based on data we have on current length
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ const fs = __importStar(require("fs"));
32
+ const path = __importStar(require("path"));
33
+ const importer_fit_1 = require("./importer.fit");
34
+ describe('EventImporterFIT OOM Reproduction', () => {
35
+ const oomSamplesDir = path.resolve(__dirname, '../../../../../samples/fit/oom');
36
+ it('should parse OOM sample fit files', () => __awaiter(void 0, void 0, void 0, function* () {
37
+ if (!fs.existsSync(oomSamplesDir)) {
38
+ console.warn(`OOM Samples directory not found at ${oomSamplesDir}. Skipping reproduction.`);
39
+ return;
40
+ }
41
+ const files = fs.readdirSync(oomSamplesDir).filter(f => f.endsWith('.fit'));
42
+ for (const file of files) {
43
+ console.log(`>>> START Processing ${file}`);
44
+ const filePath = path.join(oomSamplesDir, file);
45
+ const fileBuffer = fs.readFileSync(filePath);
46
+ const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength);
47
+ console.log(`Testing file: ${file} (${fileBuffer.length} bytes)`);
48
+ try {
49
+ const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(arrayBuffer, undefined, file);
50
+ expect(event).toBeDefined();
51
+ console.log(`✅ Successfully parsed ${file}`);
52
+ }
53
+ catch (error) {
54
+ console.error(`❌ Failed to parse ${file}:`, error);
55
+ // We don't necessarily want to throw here if we want to test all files
56
+ }
57
+ console.log(`<<< END Processing ${file}`);
58
+ }
59
+ }), 100000); // 100s timeout as these might take time if they are huge
60
+ });
@@ -153,7 +153,7 @@ class EventImporterSuuntoJSON {
153
153
  // Create the activities
154
154
  const activities = activityStartEventSamples.map((activityStartEventSample, index) => {
155
155
  const activity = new activity_1.Activity(new Date(activityStartEventSample.TimeISO8601), activityStartEventSamples.length - 1 === index
156
- ? new Date(stopEventSample ? stopEventSample.TimeISO8601 : eventJSONObject.DeviceLog.Header.TimeISO8601)
156
+ ? new Date(stopEventSample ? stopEventSample.TimeISO8601 : (eventJSONObject.DeviceLog.Header.TimeISO8601 || eventJSONObject.DeviceLog.Header.DateTime))
157
157
  : new Date(activityStartEventSamples[index + 1].TimeISO8601), activity_types_1.ActivityTypes[(importer_suunto_activity_ids_1.ImporterSuuntoActivityIds[activityStartEventSample.Events[0].Activity.ActivityType])], creator, options);
158
158
  // Set the end date to the stop event time if the activity is the last or the only one else set it on the next itery time
159
159
  // Create the stats these are a 1:1 ref arrays
@@ -38,7 +38,8 @@ export class Activity extends DurationClassAbstract {
38
38
  this.name = name;
39
39
  }
40
40
  createStream(type) {
41
- return new Stream(type, Array(ActivityUtilities.getDataLength(this.startDate, this.endDate)).fill(null));
41
+ const stream = new Stream(type, Array(ActivityUtilities.getDataLength(this.startDate, this.endDate)).fill(null));
42
+ return stream;
42
43
  }
43
44
  addDataToStream(type, date, value) {
44
45
  this.getStreamData(type)[this.getDateIndex(date)] = value;
@@ -46,7 +47,7 @@ export class Activity extends DurationClassAbstract {
46
47
  }
47
48
  addStream(stream) {
48
49
  if (this.streams.find(activityStream => activityStream.type === stream.type)) {
49
- throw new Error(`Duplicate type of stream when adding ${stream.type} to activity ${this.getID()}`);
50
+ throw new Error(`Duplicate type of stream when adding ${stream.type} to activity ${this.getID()} `);
50
51
  }
51
52
  this.streams.push(stream);
52
53
  return this;
@@ -98,7 +99,7 @@ export class Activity extends DurationClassAbstract {
98
99
  getStream(streamType) {
99
100
  const find = this.streams.find(stream => stream.type === streamType);
100
101
  if (!find) {
101
- throw Error(`No stream found with type ${streamType}`);
102
+ throw Error(`No stream found with type ${streamType} `);
102
103
  }
103
104
  return find;
104
105
  }
@@ -13,6 +13,7 @@ export declare class EventImporterFIT {
13
13
  * Generate streams samples based on lengths on an activity
14
14
  * When based on lengths, an activity do not provides sample under records object
15
15
  * @param sessionObject
16
+ * @param options
16
17
  * @private
17
18
  */
18
19
  private static generateSamplesFromLengths;
@@ -266,7 +266,7 @@ export class EventImporterFIT {
266
266
  // Note: this is how Strava generate streams for this kind of activities
267
267
  const isLengthsBased = this.isLengthsBased(sessionObject);
268
268
  const samples = isLengthsBased
269
- ? this.generateSamplesFromLengths(sessionObject)
269
+ ? this.generateSamplesFromLengths(sessionObject, options)
270
270
  : fitDataObject.records.filter((record) => {
271
271
  return record.timestamp >= activity.startDate && record.timestamp <= activity.endDate;
272
272
  });
@@ -345,9 +345,10 @@ export class EventImporterFIT {
345
345
  * Generate streams samples based on lengths on an activity
346
346
  * When based on lengths, an activity do not provides sample under records object
347
347
  * @param sessionObject
348
+ * @param options
348
349
  * @private
349
350
  */
350
- static generateSamplesFromLengths(sessionObject) {
351
+ static generateSamplesFromLengths(sessionObject, options) {
351
352
  if (!this.isLengthsBased(sessionObject)) {
352
353
  throw new ParsingEventLibError('Trying to get samples from activities lengths, but no lengths is available');
353
354
  }
@@ -364,12 +365,18 @@ export class EventImporterFIT {
364
365
  lap.lengths.forEach((length) => {
365
366
  // Resolve start/end date of current length
366
367
  const lengthStartDate = length.start_time;
367
- const lengthEndDate = new Date(lengthStartDate.getTime() + (length.total_timer_time || length.total_elapsed_time || 0) * 1000);
368
+ const lengthDuration = (length.total_timer_time || length.total_elapsed_time || 0);
369
+ const lengthEndDate = new Date(lengthStartDate.getTime() + lengthDuration * 1000);
370
+ // We check if length is valid comparing to max activity duration
371
+ if (lengthDuration > options.maxActivityDurationDays * 24 * 60 * 60) {
372
+ return;
373
+ }
368
374
  if (lengthEndDate.getTime() <= lengthStartDate.getTime()) {
369
375
  return;
370
376
  }
371
377
  // Generate a stream from length start date to end date filled by null values
372
- let lengthStream = Array(ActivityUtilities.getDataLength(lengthStartDate, lengthEndDate)).fill(null);
378
+ const streamLength = ActivityUtilities.getDataLength(lengthStartDate, lengthEndDate);
379
+ let lengthStream = Array(streamLength).fill(null);
373
380
  // Define distance step to be used for distance stream
374
381
  const lengthStepSize = lengthMeters / (lengthStream.length - 1);
375
382
  // Generate the length stream based on data we have on current length
@@ -0,0 +1,39 @@
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
+ };
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { EventImporterFIT } from './importer.fit';
13
+ describe('EventImporterFIT OOM Reproduction', () => {
14
+ const oomSamplesDir = path.resolve(__dirname, '../../../../../samples/fit/oom');
15
+ it('should parse OOM sample fit files', () => __awaiter(void 0, void 0, void 0, function* () {
16
+ if (!fs.existsSync(oomSamplesDir)) {
17
+ console.warn(`OOM Samples directory not found at ${oomSamplesDir}. Skipping reproduction.`);
18
+ return;
19
+ }
20
+ const files = fs.readdirSync(oomSamplesDir).filter(f => f.endsWith('.fit'));
21
+ for (const file of files) {
22
+ console.log(`>>> START Processing ${file}`);
23
+ const filePath = path.join(oomSamplesDir, file);
24
+ const fileBuffer = fs.readFileSync(filePath);
25
+ const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength);
26
+ console.log(`Testing file: ${file} (${fileBuffer.length} bytes)`);
27
+ try {
28
+ const event = yield EventImporterFIT.getFromArrayBuffer(arrayBuffer, undefined, file);
29
+ expect(event).toBeDefined();
30
+ console.log(`✅ Successfully parsed ${file}`);
31
+ }
32
+ catch (error) {
33
+ console.error(`❌ Failed to parse ${file}:`, error);
34
+ // We don't necessarily want to throw here if we want to test all files
35
+ }
36
+ console.log(`<<< END Processing ${file}`);
37
+ }
38
+ }), 100000); // 100s timeout as these might take time if they are huge
39
+ });
@@ -150,7 +150,7 @@ export class EventImporterSuuntoJSON {
150
150
  // Create the activities
151
151
  const activities = activityStartEventSamples.map((activityStartEventSample, index) => {
152
152
  const activity = new Activity(new Date(activityStartEventSample.TimeISO8601), activityStartEventSamples.length - 1 === index
153
- ? new Date(stopEventSample ? stopEventSample.TimeISO8601 : eventJSONObject.DeviceLog.Header.TimeISO8601)
153
+ ? new Date(stopEventSample ? stopEventSample.TimeISO8601 : (eventJSONObject.DeviceLog.Header.TimeISO8601 || eventJSONObject.DeviceLog.Header.DateTime))
154
154
  : new Date(activityStartEventSamples[index + 1].TimeISO8601), ActivityTypes[(ImporterSuuntoActivityIds[activityStartEventSample.Events[0].Activity.ActivityType])], creator, options);
155
155
  // Set the end date to the stop event time if the activity is the last or the only one else set it on the next itery time
156
156
  // Create the stats these are a 1:1 ref arrays
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sports-alliance/sports-lib",
3
- "version": "6.1.12",
3
+ "version": "6.1.14",
4
4
  "description": "A Library to for importing / exporting and processing GPX, TCX, FIT and JSON files from services such as Strava, Movescount, Garmin, Polar etc",
5
5
  "keywords": [
6
6
  "gpx",
@@ -0,0 +1,35 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { EventImporterFIT } from './src/events/adapters/importers/fit/importer.fit';
4
+
5
+ async function reproduce() {
6
+ const oomSamplesDir = path.resolve(__dirname, 'samples/fit/oom');
7
+
8
+ if (!fs.existsSync(oomSamplesDir)) {
9
+ console.warn(`OOM Samples directory not found at ${oomSamplesDir}.`);
10
+ return;
11
+ }
12
+
13
+ const files = fs.readdirSync(oomSamplesDir).filter(f => f.endsWith('.fit'));
14
+
15
+ for (const file of files) {
16
+ process.stdout.write(`>>> START Processing ${file}\n`);
17
+ const filePath = path.join(oomSamplesDir, file);
18
+ const fileBuffer = fs.readFileSync(filePath);
19
+ const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength);
20
+
21
+ console.log(`Testing file: ${file} (${fileBuffer.length} bytes)`);
22
+ try {
23
+ const event = await EventImporterFIT.getFromArrayBuffer(arrayBuffer, undefined, file);
24
+ console.log(`✅ Successfully parsed ${file}`);
25
+ } catch (error) {
26
+ console.error(`❌ Failed to parse ${file}:`, error);
27
+ }
28
+ process.stdout.write(`<<< END Processing ${file}\n`);
29
+ }
30
+ }
31
+
32
+ reproduce().catch(err => {
33
+ console.error('FATAL ERROR:', err);
34
+ process.exit(1);
35
+ });