incyclist-services 1.7.71 → 1.7.73
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/lib/cjs/devices/ride/service.js +7 -0
- package/lib/cjs/ride/display/service.js +4 -4
- package/lib/cjs/ride/page/service.js +18 -0
- package/lib/cjs/ride/route/RouteDisplayService.js +1 -1
- package/lib/cjs/routes/list/cards/RouteCard.js +11 -1
- package/lib/cjs/workouts/list/index.js +1 -0
- package/lib/cjs/workouts/list/pageService.js +98 -0
- package/lib/esm/activities/upload/trainingpeaks.js +123 -0
- package/lib/esm/apps/base/api/trainingpeaks/api.js +143 -0
- package/lib/esm/apps/base/api/trainingpeaks/index.js +1 -0
- package/lib/esm/apps/base/api/trainingpeaks/types.js +1 -0
- package/lib/esm/apps/trainingpeaks/TrainingPeaksAppConnection.js +189 -0
- package/lib/esm/apps/trainingpeaks/types.js +1 -0
- package/lib/esm/devices/ride/service.js +7 -0
- package/lib/esm/ride/display/service.js +4 -4
- package/lib/esm/ride/page/service.js +18 -0
- package/lib/esm/ride/route/RouteDisplayService.js +1 -1
- package/lib/esm/routes/list/cards/RouteCard.js +11 -1
- package/lib/esm/workouts/calendar/sync/trainingpeaks/provider.js +201 -0
- package/lib/esm/workouts/list/index.js +1 -0
- package/lib/esm/workouts/list/pageService.js +94 -0
- package/lib/types/activities/upload/trainingpeaks.d.ts +16 -0
- package/lib/types/apps/base/api/trainingpeaks/api.d.ts +30 -0
- package/lib/types/apps/base/api/trainingpeaks/index.d.ts +2 -0
- package/lib/types/apps/base/api/trainingpeaks/types.d.ts +55 -0
- package/lib/types/apps/trainingpeaks/TrainingPeaksAppConnection.d.ts +22 -0
- package/lib/types/apps/trainingpeaks/types.d.ts +6 -0
- package/lib/types/appstate/types.d.ts +1 -1
- package/lib/types/ride/page/service.d.ts +1 -0
- package/lib/types/ride/page/types.d.ts +2 -1
- package/lib/types/routes/list/cards/RouteCard.d.ts +1 -0
- package/lib/types/routes/list/cards/types.d.ts +1 -0
- package/lib/types/workouts/calendar/sync/trainingpeaks/provider.d.ts +22 -0
- package/lib/types/workouts/list/index.d.ts +1 -0
- package/lib/types/workouts/list/pageService.d.ts +18 -0
- package/package.json +2 -2
|
@@ -1362,8 +1362,15 @@ let DeviceRideService = (() => {
|
|
|
1362
1362
|
canEnforceSimulator() {
|
|
1363
1363
|
const selected = (0, routes_1.getRouteList)().getSelected();
|
|
1364
1364
|
const personalApiKey = this.getGoogleMaps().hasPersonalApiKey();
|
|
1365
|
+
if (!selected?.description)
|
|
1366
|
+
return false;
|
|
1365
1367
|
if (selected?.description?.hasVideo || personalApiKey)
|
|
1366
1368
|
return true;
|
|
1369
|
+
if (!selected.description.hasVideo) {
|
|
1370
|
+
const rideView = (0, settings_1.useUserSettings)().getValue('preferences.rideView', null);
|
|
1371
|
+
if (rideView === 'map')
|
|
1372
|
+
return true;
|
|
1373
|
+
}
|
|
1367
1374
|
return (0, settings_1.useUserSettings)().isNewUser();
|
|
1368
1375
|
}
|
|
1369
1376
|
logEvent(event) {
|
|
@@ -377,7 +377,7 @@ let RideDisplayService = (() => {
|
|
|
377
377
|
return { ...props, ...childProps };
|
|
378
378
|
}
|
|
379
379
|
catch (err) {
|
|
380
|
-
this.logError(err, 'getDisplayProperties');
|
|
380
|
+
this.logError(err, 'getDisplayProperties', { state: this.state });
|
|
381
381
|
return { state: this.state };
|
|
382
382
|
}
|
|
383
383
|
}
|
|
@@ -425,7 +425,7 @@ let RideDisplayService = (() => {
|
|
|
425
425
|
async stopRide(props = {}) {
|
|
426
426
|
try {
|
|
427
427
|
if (this.state !== 'Starting' && this.state !== 'Idle') {
|
|
428
|
-
this.logEvent({ message: "activity stopped", activity: this.activity?.id });
|
|
428
|
+
this.logEvent({ message: "activity stopped", activity: this.activity?.id, lastState: this.state });
|
|
429
429
|
}
|
|
430
430
|
const prevState = this.state;
|
|
431
431
|
this.state = prevState === 'Starting' ? 'Closing' : 'Finished';
|
|
@@ -449,7 +449,6 @@ let RideDisplayService = (() => {
|
|
|
449
449
|
}
|
|
450
450
|
if (prevState !== 'Starting')
|
|
451
451
|
this.enableScreensaver();
|
|
452
|
-
this.state = 'Idle';
|
|
453
452
|
}
|
|
454
453
|
catch (err) {
|
|
455
454
|
this.logError(err, 'stopRide');
|
|
@@ -458,9 +457,10 @@ let RideDisplayService = (() => {
|
|
|
458
457
|
}
|
|
459
458
|
async closePrevRide() {
|
|
460
459
|
try {
|
|
461
|
-
if (this.observer || (this.state !== 'Idle')) {
|
|
460
|
+
if (this.observer || (this.state !== 'Idle' && this.state !== 'Finished')) {
|
|
462
461
|
await this.stopRide({ noStateUpdates: true });
|
|
463
462
|
}
|
|
463
|
+
this.state = 'Idle';
|
|
464
464
|
}
|
|
465
465
|
catch (err) {
|
|
466
466
|
this.logError(err, 'init');
|
|
@@ -196,6 +196,12 @@ let RidePageService = (() => {
|
|
|
196
196
|
}
|
|
197
197
|
onMenuClose() {
|
|
198
198
|
try {
|
|
199
|
+
const state = this.getRideDisplay().getState();
|
|
200
|
+
if (state === 'Finished' || this.menuProps.finished) {
|
|
201
|
+
this.moveToPreviousPage();
|
|
202
|
+
this.closePage();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
199
205
|
this.menuProps = null;
|
|
200
206
|
this.updatePageDisplay();
|
|
201
207
|
}
|
|
@@ -223,6 +229,15 @@ let RidePageService = (() => {
|
|
|
223
229
|
this.logError(err, 'onResume');
|
|
224
230
|
}
|
|
225
231
|
}
|
|
232
|
+
onFinished() {
|
|
233
|
+
try {
|
|
234
|
+
this.menuProps = { showResume: false, finished: true };
|
|
235
|
+
this.updatePageDisplay();
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
this.logError(err, 'onResume');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
226
241
|
onEndRide() {
|
|
227
242
|
try {
|
|
228
243
|
this.getRideDisplay().stop();
|
|
@@ -336,6 +351,9 @@ let RidePageService = (() => {
|
|
|
336
351
|
case 'Paused':
|
|
337
352
|
this.menuProps = { showResume: true };
|
|
338
353
|
break;
|
|
354
|
+
case 'Finished':
|
|
355
|
+
this.menuProps = { showResume: false, finished: true };
|
|
356
|
+
break;
|
|
339
357
|
case 'Active':
|
|
340
358
|
this.menuProps = null;
|
|
341
359
|
break;
|
|
@@ -276,7 +276,7 @@ let RouteDisplayService = (() => {
|
|
|
276
276
|
const mode = this.getDeviceRide().getCyclingMode();
|
|
277
277
|
const isSIM = mode?.isSIM();
|
|
278
278
|
const realityFactor = this.startSettings?.realityFactor ?? 100;
|
|
279
|
-
const targetSlope = (this.position
|
|
279
|
+
const targetSlope = (this.position?.slope ?? 0) * realityFactor / 100;
|
|
280
280
|
if (props?.limits && !isSIM) {
|
|
281
281
|
delete this.prevRequestSlope;
|
|
282
282
|
return { ...props?.limits, slope: targetSlope };
|
|
@@ -53,6 +53,7 @@ const monitoring_1 = require("../../../monitoring");
|
|
|
53
53
|
const geo_1 = require("../../../utils/geo");
|
|
54
54
|
const i18n_1 = require("../../../i18n");
|
|
55
55
|
const decorators_1 = require("../../../base/decorators");
|
|
56
|
+
const appstate_1 = require("../../../appstate");
|
|
56
57
|
exports.DEFAULT_TITLE = 'Import Route';
|
|
57
58
|
exports.DEFAULT_FILTERS = [
|
|
58
59
|
{ name: 'Routes', extensions: ['gpx', 'epm', 'xml', 'rlv', 'pgmf'] },
|
|
@@ -79,13 +80,16 @@ let RouteCard = (() => {
|
|
|
79
80
|
let _instanceExtraInitializers = [];
|
|
80
81
|
let _getBindings_decorators;
|
|
81
82
|
let _getOnlineStatusMonitoring_decorators;
|
|
83
|
+
let _getAppState_decorators;
|
|
82
84
|
return class RouteCard extends _classSuper {
|
|
83
85
|
static {
|
|
84
86
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
85
87
|
_getBindings_decorators = [decorators_1.Injectable];
|
|
86
88
|
_getOnlineStatusMonitoring_decorators = [decorators_1.Injectable];
|
|
89
|
+
_getAppState_decorators = [decorators_1.Injectable];
|
|
87
90
|
__esDecorate(this, null, _getBindings_decorators, { kind: "method", name: "getBindings", static: false, private: false, access: { has: obj => "getBindings" in obj, get: obj => obj.getBindings }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
88
91
|
__esDecorate(this, null, _getOnlineStatusMonitoring_decorators, { kind: "method", name: "getOnlineStatusMonitoring", static: false, private: false, access: { has: obj => "getOnlineStatusMonitoring" in obj, get: obj => obj.getOnlineStatusMonitoring }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
92
|
+
__esDecorate(this, null, _getAppState_decorators, { kind: "method", name: "getAppState", static: false, private: false, access: { has: obj => "getAppState" in obj, get: obj => obj.getAppState }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
89
93
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
90
94
|
}
|
|
91
95
|
downloadObserver = __runInitializers(this, _instanceExtraInitializers);
|
|
@@ -370,11 +374,14 @@ let RouteCard = (() => {
|
|
|
370
374
|
}
|
|
371
375
|
let showLoopOverwrite, showNextOverwrite;
|
|
372
376
|
let hasWorkout = true;
|
|
377
|
+
let showWorkoutOption = false;
|
|
373
378
|
try {
|
|
374
379
|
showLoopOverwrite = this.route?.description?.isLoop;
|
|
375
380
|
const next = (0, route_1.getNextVideoId)(this.route);
|
|
376
381
|
showNextOverwrite = !!next;
|
|
377
382
|
hasWorkout = (0, valid_1.valid)(workouts.getSelected());
|
|
383
|
+
const workoutsEnabled = this.isMobile() ? this.getAppState().hasFeature('MOBILE_WORKOUTS') : true;
|
|
384
|
+
showWorkoutOption = workoutsEnabled && !hasWorkout;
|
|
378
385
|
if (showNextOverwrite) {
|
|
379
386
|
const card = (0, service_1.getRouteList)().getCard(next);
|
|
380
387
|
const route = card?.getData();
|
|
@@ -392,7 +399,7 @@ let RouteCard = (() => {
|
|
|
392
399
|
const totalElevation = { value: C(this.route.description.elevation, 'elevation', { digits: 0 }), unit: uEl };
|
|
393
400
|
const xScale = { value: C(1, 'distance'), unit: uDist };
|
|
394
401
|
const yScale = { value: C(1, 'elevation'), unit: uEl };
|
|
395
|
-
return { settings: uiSettings, totalDistance, totalElevation, showLoopOverwrite, showNextOverwrite, hasWorkout, canStart, videoChecking, videoMissing,
|
|
402
|
+
return { settings: uiSettings, totalDistance, totalElevation, showLoopOverwrite, showNextOverwrite, hasWorkout, showWorkoutOption, canStart, videoChecking, videoMissing,
|
|
396
403
|
xScale, yScale,
|
|
397
404
|
updateStartPos: this.updateStartPos.bind(this),
|
|
398
405
|
updateMarkers: this.updateMarkers.bind(this)
|
|
@@ -909,6 +916,9 @@ let RouteCard = (() => {
|
|
|
909
916
|
getOnlineStatusMonitoring() {
|
|
910
917
|
return (0, monitoring_1.useOnlineStatusMonitoring)();
|
|
911
918
|
}
|
|
919
|
+
getAppState() {
|
|
920
|
+
return (0, appstate_1.useAppState)();
|
|
921
|
+
}
|
|
912
922
|
getRouteDownload() {
|
|
913
923
|
return (0, service_2.useRouteDownload)();
|
|
914
924
|
}
|
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./service"), exports);
|
|
18
18
|
__exportStar(require("./cards"), exports);
|
|
19
|
+
__exportStar(require("./pageService"), exports);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
+
var useValue = arguments.length > 2;
|
|
4
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
+
}
|
|
7
|
+
return useValue ? value : void 0;
|
|
8
|
+
};
|
|
9
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
+
var _, done = false;
|
|
15
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
+
var context = {};
|
|
17
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
+
if (kind === "accessor") {
|
|
22
|
+
if (result === void 0) continue;
|
|
23
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
+
}
|
|
28
|
+
else if (_ = accept(result)) {
|
|
29
|
+
if (kind === "field") initializers.unshift(_);
|
|
30
|
+
else descriptor[key] = _;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
+
done = true;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.getWorkoutListPageService = exports.WorkoutListPageService = void 0;
|
|
38
|
+
const types_1 = require("../../base/types");
|
|
39
|
+
const pages_1 = require("../../base/pages");
|
|
40
|
+
const decorators_1 = require("../../base/decorators");
|
|
41
|
+
const appstate_1 = require("../../appstate");
|
|
42
|
+
let WorkoutListPageService = (() => {
|
|
43
|
+
let _classDecorators = [types_1.Singleton];
|
|
44
|
+
let _classDescriptor;
|
|
45
|
+
let _classExtraInitializers = [];
|
|
46
|
+
let _classThis;
|
|
47
|
+
let _classSuper = pages_1.IncyclistPageService;
|
|
48
|
+
let _instanceExtraInitializers = [];
|
|
49
|
+
let _getAppState_decorators;
|
|
50
|
+
var WorkoutListPageService = class extends _classSuper {
|
|
51
|
+
static { _classThis = this; }
|
|
52
|
+
static {
|
|
53
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
54
|
+
_getAppState_decorators = [decorators_1.Injectable];
|
|
55
|
+
__esDecorate(this, null, _getAppState_decorators, { kind: "method", name: "getAppState", static: false, private: false, access: { has: obj => "getAppState" in obj, get: obj => obj.getAppState }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
56
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
57
|
+
WorkoutListPageService = _classThis = _classDescriptor.value;
|
|
58
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
59
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
60
|
+
}
|
|
61
|
+
constructor() {
|
|
62
|
+
super('WorkoutListPage');
|
|
63
|
+
__runInitializers(this, _instanceExtraInitializers);
|
|
64
|
+
}
|
|
65
|
+
openPage() {
|
|
66
|
+
try {
|
|
67
|
+
super.openPage();
|
|
68
|
+
this.logEvent({ message: 'page shown', page: 'Workouts' });
|
|
69
|
+
return this.getPageObserver();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
this.logError(err, 'openPage');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
closePage() {
|
|
76
|
+
try {
|
|
77
|
+
this.logEvent({ message: 'page closed', page: 'Workouts' });
|
|
78
|
+
super.closePage();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
this.logError(err, 'closePage');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
getPageDisplayProps() {
|
|
85
|
+
if (!this.getAppState().hasFeature('MOBILE_WORKOUTS')) {
|
|
86
|
+
return { pageType: 'placeholder' };
|
|
87
|
+
}
|
|
88
|
+
return { pageType: 'list' };
|
|
89
|
+
}
|
|
90
|
+
getAppState() {
|
|
91
|
+
return (0, appstate_1.useAppState)();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
return WorkoutListPageService = _classThis;
|
|
95
|
+
})();
|
|
96
|
+
exports.WorkoutListPageService = WorkoutListPageService;
|
|
97
|
+
const getWorkoutListPageService = () => new WorkoutListPageService();
|
|
98
|
+
exports.getWorkoutListPageService = getWorkoutListPageService;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { Injectable } from '../../base/decorators/Injection';
|
|
36
|
+
import { IncyclistService } from '../../base/service';
|
|
37
|
+
import { Singleton } from '../../base/types';
|
|
38
|
+
import { TrainingPeaksAppConnection } from '../../apps/trainingpeaks/TrainingPeaksAppConnection';
|
|
39
|
+
import { ActivityUploadFactory } from './factory';
|
|
40
|
+
let TrainingPeaksUpload = (() => {
|
|
41
|
+
let _classDecorators = [Singleton];
|
|
42
|
+
let _classDescriptor;
|
|
43
|
+
let _classExtraInitializers = [];
|
|
44
|
+
let _classThis;
|
|
45
|
+
let _classSuper = IncyclistService;
|
|
46
|
+
let _instanceExtraInitializers = [];
|
|
47
|
+
let _getConnection_decorators;
|
|
48
|
+
var TrainingPeaksUpload = class extends _classSuper {
|
|
49
|
+
static { _classThis = this; }
|
|
50
|
+
static {
|
|
51
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
52
|
+
_getConnection_decorators = [Injectable];
|
|
53
|
+
__esDecorate(this, null, _getConnection_decorators, { kind: "method", name: "getConnection", static: false, private: false, access: { has: obj => "getConnection" in obj, get: obj => obj.getConnection }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
54
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
55
|
+
TrainingPeaksUpload = _classThis = _classDescriptor.value;
|
|
56
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
57
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
58
|
+
}
|
|
59
|
+
isInitialized = __runInitializers(this, _instanceExtraInitializers);
|
|
60
|
+
connection = new TrainingPeaksAppConnection();
|
|
61
|
+
constructor() {
|
|
62
|
+
super('TrainingPeaksUpload');
|
|
63
|
+
this.isInitialized = false;
|
|
64
|
+
this.init();
|
|
65
|
+
}
|
|
66
|
+
init() {
|
|
67
|
+
if (this.isInitialized)
|
|
68
|
+
return true;
|
|
69
|
+
this.isInitialized = this.getConnection().init();
|
|
70
|
+
return this.isInitialized;
|
|
71
|
+
}
|
|
72
|
+
isConnected() {
|
|
73
|
+
return this.getConnection().isConnected();
|
|
74
|
+
}
|
|
75
|
+
async upload(activity, format = 'TCX') {
|
|
76
|
+
try {
|
|
77
|
+
const ok = this.ensureInitialized();
|
|
78
|
+
if (!ok) {
|
|
79
|
+
this.logEvent({ message: 'TrainingPeaks upload skipped', reason: 'not initialized' });
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (!this.isConnected())
|
|
83
|
+
return false;
|
|
84
|
+
const lcFormat = format.toLowerCase();
|
|
85
|
+
const ucFormat = format.toUpperCase();
|
|
86
|
+
this.logEvent({ message: 'TrainingPeaks upload', format: ucFormat });
|
|
87
|
+
if (!activity.links)
|
|
88
|
+
activity.links = {};
|
|
89
|
+
const fileName = activity[`${lcFormat}FileName`];
|
|
90
|
+
const res = await this.getApi().upload(fileName, { name: activity.title, description: '' });
|
|
91
|
+
this.logEvent({ message: 'TrainingPeaks upload success', activityId: res.trainingPeaksId });
|
|
92
|
+
activity.links.trainingpeaks = {
|
|
93
|
+
activity_id: String(res.trainingPeaksId),
|
|
94
|
+
url: this.getUrl(res.trainingPeaksId)
|
|
95
|
+
};
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
this.logEvent({ message: 'TrainingPeaks upload failure', error: err.message });
|
|
100
|
+
activity.links.trainingpeaks = { error: err.message };
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
getUrl(activityId) {
|
|
105
|
+
return `https://www.trainingpeaks.com/athlete/workouts/${activityId}`;
|
|
106
|
+
}
|
|
107
|
+
ensureInitialized() {
|
|
108
|
+
if (!this.isInitialized)
|
|
109
|
+
this.init();
|
|
110
|
+
return this.isInitialized;
|
|
111
|
+
}
|
|
112
|
+
getConnection() {
|
|
113
|
+
return this.connection;
|
|
114
|
+
}
|
|
115
|
+
getApi() {
|
|
116
|
+
return this.getConnection().getApi();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return TrainingPeaksUpload = _classThis;
|
|
120
|
+
})();
|
|
121
|
+
export { TrainingPeaksUpload };
|
|
122
|
+
const factory = new ActivityUploadFactory();
|
|
123
|
+
factory.add('trainingpeaks', new TrainingPeaksUpload());
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { EventLogger } from 'gd-eventlog';
|
|
2
|
+
import { Observer } from '../../../../base/types/observer';
|
|
3
|
+
import { valid } from '../../../../utils/valid';
|
|
4
|
+
import { sleep } from '../../../../utils/sleep';
|
|
5
|
+
import { AppApiBase } from '../base';
|
|
6
|
+
const UPLOAD_POLL_INTERVAL_MS = 2000;
|
|
7
|
+
const UPLOAD_TIMEOUT_MS = 60000;
|
|
8
|
+
export class TrainingPeaksApi extends AppApiBase {
|
|
9
|
+
config;
|
|
10
|
+
logger;
|
|
11
|
+
observer;
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.logger = new EventLogger('TrainingPeaksApi');
|
|
15
|
+
this.observer = new Observer();
|
|
16
|
+
}
|
|
17
|
+
getBaseUrl() {
|
|
18
|
+
return process.env.TRAININGPEAKS_API || 'https://api.sandbox.trainingpeaks.com';
|
|
19
|
+
}
|
|
20
|
+
getOauthBaseUrl() {
|
|
21
|
+
return process.env.TRAININGPEAKS_OAUTH_URL || 'https://oauth.sandbox.trainingpeaks.com';
|
|
22
|
+
}
|
|
23
|
+
init(config) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
return this.observer;
|
|
26
|
+
}
|
|
27
|
+
update(config) {
|
|
28
|
+
if (!this.config)
|
|
29
|
+
throw new Error('update not allowed before init');
|
|
30
|
+
this.config = { ...this.config, ...config };
|
|
31
|
+
}
|
|
32
|
+
isAuthenticated() {
|
|
33
|
+
return valid(this.config?.accessToken);
|
|
34
|
+
}
|
|
35
|
+
async getLoggedInAthlete() {
|
|
36
|
+
await this.verifyAuthentication();
|
|
37
|
+
const response = await this.get('/v1/athlete/profile');
|
|
38
|
+
return response.data;
|
|
39
|
+
}
|
|
40
|
+
async getCalendarWorkouts(opts) {
|
|
41
|
+
await this.verifyAuthentication();
|
|
42
|
+
const oldest = opts?.oldest ?? new Date();
|
|
43
|
+
const newest = opts?.newest ?? new Date(oldest.valueOf() + 30 * 24 * 60 * 60 * 1000);
|
|
44
|
+
const fmt = (d) => d.toISOString().slice(0, 10);
|
|
45
|
+
const response = await this.get(`/v2/workouts/${fmt(oldest)}/${fmt(newest)}`);
|
|
46
|
+
return response.data;
|
|
47
|
+
}
|
|
48
|
+
async getWorkoutFile(id, format = 'zwo') {
|
|
49
|
+
await this.verifyAuthentication();
|
|
50
|
+
const response = await this.get(`/v2/workouts/wod/file/${id}?format=${format}`);
|
|
51
|
+
return response.data;
|
|
52
|
+
}
|
|
53
|
+
async upload(fileName, props) {
|
|
54
|
+
await this.verifyAuthentication();
|
|
55
|
+
const reqOpts = {
|
|
56
|
+
headers: { Authorization: `bearer ${this.config.accessToken}` }
|
|
57
|
+
};
|
|
58
|
+
const form = await this.createForm('/v3/file', { file: { type: 'file', fileName } }, reqOpts);
|
|
59
|
+
const response = await this.postForm(form);
|
|
60
|
+
const location = response.headers?.['location'] ?? '';
|
|
61
|
+
const trackingId = location.split('/').filter(Boolean).pop();
|
|
62
|
+
if (!trackingId) {
|
|
63
|
+
throw new Error('TrainingPeaks upload failed: no tracking ID returned');
|
|
64
|
+
}
|
|
65
|
+
return this.waitForUploadComplete(trackingId, props?.externalId);
|
|
66
|
+
}
|
|
67
|
+
async getUploadStatus(trackingId) {
|
|
68
|
+
await this.verifyAuthentication();
|
|
69
|
+
const response = await this.get(`/v3/status/${trackingId}`);
|
|
70
|
+
return response.data;
|
|
71
|
+
}
|
|
72
|
+
async verifyAuthentication() {
|
|
73
|
+
if (!this.isAuthenticated())
|
|
74
|
+
throw new Error('not authenticated');
|
|
75
|
+
await this.verifyToken();
|
|
76
|
+
}
|
|
77
|
+
async verifyToken() {
|
|
78
|
+
if (!this.isTokenStillValid()) {
|
|
79
|
+
try {
|
|
80
|
+
await this.refreshToken();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new Error('token refresh failed');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
isTokenStillValid() {
|
|
88
|
+
if (!this.config?.expiration)
|
|
89
|
+
return false;
|
|
90
|
+
return Date.now() < this.config.expiration.valueOf();
|
|
91
|
+
}
|
|
92
|
+
async refreshToken() {
|
|
93
|
+
this.logger.logEvent({ message: 'TrainingPeaks refresh token start' });
|
|
94
|
+
try {
|
|
95
|
+
const response = await this.getApi().request({
|
|
96
|
+
method: 'post',
|
|
97
|
+
url: `${this.getOauthBaseUrl()}/oauth/token`,
|
|
98
|
+
data: new URLSearchParams({
|
|
99
|
+
grant_type: 'refresh_token',
|
|
100
|
+
refresh_token: this.config.refreshToken,
|
|
101
|
+
client_id: this.config.clientId,
|
|
102
|
+
client_secret: this.config.clientSecret,
|
|
103
|
+
}).toString(),
|
|
104
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
105
|
+
});
|
|
106
|
+
const data = response.data;
|
|
107
|
+
this.config.accessToken = data.access_token;
|
|
108
|
+
this.config.refreshToken = data.refresh_token;
|
|
109
|
+
this.config.expiration = new Date(Date.now() + data.expires_in * 1000);
|
|
110
|
+
this.logger.logEvent({ message: 'TrainingPeaks refresh token success' });
|
|
111
|
+
this.observer.emit('token.updated', this.config);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
this.logger.logEvent({ message: 'TrainingPeaks refresh token error', error: err.message });
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async waitForUploadComplete(trackingId, externalId) {
|
|
119
|
+
const deadline = Date.now() + UPLOAD_TIMEOUT_MS;
|
|
120
|
+
while (Date.now() < deadline) {
|
|
121
|
+
const status = await this.getUploadStatus(trackingId);
|
|
122
|
+
if (status.Completed) {
|
|
123
|
+
if (!status.WorkoutIds?.length) {
|
|
124
|
+
throw new Error(`TrainingPeaks upload failed: ${status.Status}`);
|
|
125
|
+
}
|
|
126
|
+
return { trainingPeaksId: status.WorkoutIds[0], externalId };
|
|
127
|
+
}
|
|
128
|
+
await sleep(UPLOAD_POLL_INTERVAL_MS);
|
|
129
|
+
}
|
|
130
|
+
throw new Error('TrainingPeaks upload timed out');
|
|
131
|
+
}
|
|
132
|
+
async get(url, config) {
|
|
133
|
+
const props = config ?? {};
|
|
134
|
+
const response = await this.getApi().request({
|
|
135
|
+
method: 'get',
|
|
136
|
+
url: this.getBaseUrl() + url,
|
|
137
|
+
headers: { Authorization: `bearer ${this.config.accessToken}` },
|
|
138
|
+
validateStatus: (status) => (status >= 200 && status < 300) || status === 403,
|
|
139
|
+
...props
|
|
140
|
+
});
|
|
141
|
+
return response;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TrainingPeaksApi } from './api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|