@gooin/garmin-connect
Version:
Makes it simple to interface with Garmin Connect to get or set any data point
469 lines • 19 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const form_data_1 = __importDefault(require("form-data"));
const lodash_1 = __importDefault(require("lodash"));
const luxon_1 = require("luxon");
const node_fs_1 = require("node:fs");
const node_path_1 = __importDefault(require("node:path"));
const HttpClient_1 = require("../common/HttpClient");
const utils_1 = require("../utils");
const UrlClass_1 = require("./UrlClass");
const DateUtils_1 = require("./common/DateUtils");
const HydrationUtils_1 = require("./common/HydrationUtils");
const WeightUtils_1 = require("./common/WeightUtils");
const types_1 = require("./types");
const Running_1 = __importDefault(require("./workouts/Running"));
class GarminConnect {
// private oauth1: OAuth;
constructor(config, domain = 'garmin.com') {
var _a, _b;
const { username, password } = config;
if (!username || !password) {
throw new Error('Missing credentials');
}
this.config = config;
this.url = new UrlClass_1.UrlClass((_a = config === null || config === void 0 ? void 0 : config.domain) !== null && _a !== void 0 ? _a : domain);
this.domain = (_b = config === null || config === void 0 ? void 0 : config.domain) !== null && _b !== void 0 ? _b : domain;
this._userHash = undefined;
this.listeners = {};
this.client = new HttpClient_1.HttpClient(this.url, config);
}
async login(username, password) {
if (username && password) {
this.config.username = username;
this.config.password = password;
}
await this.client.login(this.config.username, this.config.password);
return this;
}
async exportTokenToFile(dirPath) {
const isDir = await (0, utils_1.checkIsDirectory)(dirPath);
if (!isDir) {
await (0, utils_1.createDirectory)(dirPath);
}
// save oauth1 to json
if (this.client.oauth1Token) {
await (0, utils_1.writeToFile)(node_path_1.default.join(dirPath, 'oauth1_token.json'), JSON.stringify(this.client.oauth1Token));
}
if (this.client.oauth2Token) {
await (0, utils_1.writeToFile)(node_path_1.default.join(dirPath, 'oauth2_token.json'), JSON.stringify(this.client.oauth2Token));
}
}
async loadTokenByFile(dirPath) {
const isDir = await (0, utils_1.checkIsDirectory)(dirPath);
if (!isDir) {
throw new Error('loadTokenByFile: Directory not found: ' + dirPath);
}
let oauth1Data = await node_fs_1.promises.readFile(node_path_1.default.join(dirPath, 'oauth1_token.json'), 'utf-8');
// console.log('loadTokenByFile - oauth1Data:', oauth1Data);
const oauth1 = JSON.parse(oauth1Data);
// console.log('loadTokenByFile - oauth1:', oauth1);
this.client.oauth1Token = oauth1;
let oauth2Data = await node_fs_1.promises.readFile(node_path_1.default.join(dirPath, 'oauth2_token.json'), 'utf-8');
// console.log('loadTokenByFile - oauth2Data:', oauth2Data);
const oauth2 = JSON.parse(oauth2Data);
// console.log('loadTokenByFile - oauth2:', oauth2);
this.client.oauth2Token = oauth2;
// console.log('loadTokenByFile - oauth2Token:', this);
// console.log('loadTokenByFile - oauth2Token:', this.client.oauth2Token);
}
exportToken() {
if (!this.client.oauth1Token || !this.client.oauth2Token) {
throw new Error('exportToken: Token not found');
}
return {
oauth1: this.client.oauth1Token,
oauth2: this.client.oauth2Token
};
}
// from db or localstorage etc
loadToken(oauth1, oauth2) {
this.client.oauth1Token = oauth1;
this.client.oauth2Token = oauth2;
}
async getUserSettings() {
return this.client.get(this.url.USER_SETTINGS);
}
async getUserProfile() {
return this.client.get(this.url.USER_PROFILE);
}
async getActivities(start, limit, activityType, subActivityType) {
return this.client.get(this.url.ACTIVITIES, {
params: { start, limit, activityType, subActivityType }
});
}
async getActivity(activity) {
if (!activity.activityId)
throw new Error('Missing activityId');
return this.client.get(this.url.ACTIVITY + activity.activityId);
}
async countActivities() {
return this.client.get(this.url.STAT_ACTIVITIES, {
params: {
aggregation: 'lifetime',
startDate: '1970-01-01',
endDate: luxon_1.DateTime.now().toFormat('yyyy-MM-dd'),
metric: 'duration'
}
});
}
async downloadWellnessData(date, dir) {
const dateStr = (0, DateUtils_1.toDateString)(date);
const isDir = await (0, utils_1.checkIsDirectory)(dir);
if (!isDir) {
await (0, utils_1.createDirectory)(dir);
}
let fileBuffer = await this.client.get(this.url.DOWNLOAD_WELLNESS + dateStr, {
responseType: 'arraybuffer'
});
await (0, utils_1.writeToFile)(node_path_1.default.join(dir, `${dateStr}.zip`), fileBuffer);
}
async downloadOriginalActivityData(activity, dir, type = 'zip') {
if (!activity.activityId)
throw new Error('Missing activityId');
const isDir = await (0, utils_1.checkIsDirectory)(dir);
if (!isDir) {
await (0, utils_1.createDirectory)(dir);
}
let fileBuffer;
if (type === 'tcx') {
fileBuffer = await this.client.get(this.url.DOWNLOAD_TCX + activity.activityId);
}
else if (type === 'gpx') {
fileBuffer = await this.client.get(this.url.DOWNLOAD_GPX + activity.activityId);
}
else if (type === 'kml') {
fileBuffer = await this.client.get(this.url.DOWNLOAD_KML + activity.activityId);
}
else if (type === 'zip') {
fileBuffer = await this.client.get(this.url.DOWNLOAD_ZIP + activity.activityId, {
responseType: 'arraybuffer'
});
}
else {
throw new Error('downloadOriginalActivityData - Invalid type: ' + type);
}
await (0, utils_1.writeToFile)(node_path_1.default.join(dir, `${activity.activityId}.${type}`), fileBuffer);
}
async uploadActivity(file, format = 'fit') {
var _a;
const detectedFormat = (_a = (format || node_path_1.default.extname(file))) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (!lodash_1.default.includes(types_1.UploadFileType, detectedFormat)) {
throw new Error('uploadActivity - Invalid format: ' + format);
}
// const fh = await fs.open(file);
const fileBuffer = (0, node_fs_1.createReadStream)(file);
// console.log('fileBuffer:', fileBuffer);
const form = new form_data_1.default();
form.append('userfile', fileBuffer);
const response = await this.client.post(this.url.UPLOAD + '.' + format, form, {
headers: {
'Content-Type': form.getHeaders()['content-type']
}
});
fileBuffer.close();
return response;
}
async deleteActivity(activity) {
if (!activity.activityId)
throw new Error('Missing activityId');
await this.client.delete(this.url.ACTIVITY + activity.activityId);
}
async getWorkouts(start, limit) {
return this.client.get(this.url.WORKOUTS, {
params: {
start,
limit
}
});
}
async getWorkoutDetail(workout) {
if (!workout.workoutId)
throw new Error('Missing workoutId');
return this.client.get(this.url.WORKOUT(workout.workoutId));
}
async addWorkout(workout) {
if (!workout)
throw new Error('Missing workout');
if (workout instanceof Running_1.default) {
if (workout.isValid()) {
const data = { ...workout.toJson() };
if (!data.description) {
data.description = 'Added by garmin-connect for Node.js';
}
return this.client.post(this.url.WORKOUT(), data);
}
}
if (!workout.workoutSegments)
throw new Error('Missing workoutSegments, please use WorkoutDetail, not Workout.');
const newWorkout = lodash_1.default.omit(workout, [
'workoutId',
'ownerId',
'updatedDate',
'createdDate',
'author'
]);
if (!newWorkout.description) {
newWorkout.description = 'Added by garmin-connect for Node.js';
}
// console.log('addWorkout - newWorkout:', newWorkout)
return this.client.post(this.url.WORKOUT(), newWorkout);
}
async addRunningWorkout(name, meters, description) {
const running = new Running_1.default();
running.name = name;
running.distance = meters;
running.description = description;
return this.addWorkout(running);
}
async deleteWorkout(workout) {
if (!workout.workoutId)
throw new Error('Missing workout');
return this.client.delete(this.url.WORKOUT(workout.workoutId));
}
async scheduleWorkout(workout, date = new Date()) {
if (!workout.workoutId)
throw new Error('Missing workoutId');
const formatedDate = luxon_1.DateTime.fromJSDate(date).toFormat('yyyy-MM-dd');
return this.client.post(`${this.url.SCHEDULE_WORKOUTS}${workout.workoutId}`, {
date: formatedDate
});
}
// Garmin use month 0-11, not real month.
async getCalendar(year = new Date().getFullYear(), month = new Date().getMonth()) {
return this.client.get(this.url.CALENDAR(year, month));
}
async getSteps(date = new Date()) {
const dateString = (0, DateUtils_1.toDateString)(date);
const days = await this.client.get(`${this.url.DAILY_STEPS}${dateString}/${dateString}`);
const dayStats = days.find(({ calendarDate }) => calendarDate === dateString);
if (!dayStats) {
throw new Error("Can't find daily steps for this date.");
}
return dayStats.totalSteps;
}
async getSleepData(date = new Date()) {
try {
const dateString = (0, DateUtils_1.toDateString)(date);
const sleepData = await this.client.get(`${this.url.DAILY_SLEEP}`, { params: { date: dateString } });
if (!sleepData) {
throw new Error('Invalid or empty sleep data response.');
}
return sleepData;
}
catch (error) {
throw new Error(`Error in getSleepData: ${error.message}`);
}
}
async getSleepDuration(date = new Date()) {
try {
const sleepData = await this.getSleepData(date);
if (!sleepData ||
!sleepData.dailySleepDTO ||
sleepData.dailySleepDTO.sleepStartTimestampGMT === undefined ||
sleepData.dailySleepDTO.sleepEndTimestampGMT === undefined) {
throw new Error('Invalid or missing sleep data for the specified date.');
}
const sleepStartTimestampGMT = sleepData.dailySleepDTO.sleepStartTimestampGMT;
const sleepEndTimestampGMT = sleepData.dailySleepDTO.sleepEndTimestampGMT;
const { hours, minutes } = (0, DateUtils_1.calculateTimeDifference)(sleepStartTimestampGMT, sleepEndTimestampGMT);
return {
hours,
minutes
};
}
catch (error) {
throw new Error(`Error in getSleepDuration: ${error.message}`);
}
}
async getDailyWeightData(date = new Date()) {
try {
const dateString = (0, DateUtils_1.toDateString)(date);
const weightData = await this.client.get(`${this.url.DAILY_WEIGHT}/${dateString}`);
if (!weightData) {
throw new Error('Invalid or empty weight data response.');
}
return weightData;
}
catch (error) {
throw new Error(`Error in getDailyWeightData: ${error.message}`);
}
}
async getDailyWeightInPounds(date = new Date()) {
const weightData = await this.getDailyWeightData(date);
if (weightData.totalAverage &&
typeof weightData.totalAverage.weight === 'number') {
return (0, WeightUtils_1.gramsToPounds)(weightData.totalAverage.weight);
}
else {
throw new Error("Can't find valid daily weight for this date.");
}
}
async getDailyHydration(date = new Date()) {
try {
const dateString = (0, DateUtils_1.toDateString)(date);
const hydrationData = await this.client.get(`${this.url.DAILY_HYDRATION}/${dateString}`);
if (!hydrationData || !hydrationData.valueInML) {
throw new Error('Invalid or empty hydration data response.');
}
return (0, HydrationUtils_1.convertMLToOunces)(hydrationData.valueInML);
}
catch (error) {
throw new Error(`Error in getDailyHydration: ${error.message}`);
}
}
async updateWeight(date = new Date(), lbs, timezone) {
try {
const weightData = await this.client.post(`${this.url.UPDATE_WEIGHT}`, {
dateTimestamp: (0, DateUtils_1.getLocalTimestamp)(date, timezone),
gmtTimestamp: date.toISOString().substring(0, 23),
unitKey: 'lbs',
value: lbs
});
return weightData;
}
catch (error) {
throw new Error(`Error in updateWeight: ${error.message}`);
}
}
async updateHydrationLogOunces(date = new Date(), valueInOz) {
try {
const dateString = (0, DateUtils_1.toDateString)(date);
const hydrationData = await this.client.put(`${this.url.HYDRATION_LOG}`, {
calendarDate: dateString,
valueInML: (0, HydrationUtils_1.convertOuncesToML)(valueInOz),
userProfileId: (await this.getUserProfile()).profileId,
timestampLocal: date.toISOString().substring(0, 23)
});
return hydrationData;
}
catch (error) {
throw new Error(`Error in updateHydrationLogOunces: ${error.message}`);
}
}
async getGolfSummary() {
try {
const golfSummary = await this.client.get(`${this.url.GOLF_SCORECARD_SUMMARY}`);
if (!golfSummary) {
throw new Error('Invalid or empty golf summary data response.');
}
return golfSummary;
}
catch (error) {
throw new Error(`Error in getGolfSummary: ${error.message}`);
}
}
async getGolfScorecard(scorecardId) {
try {
const golfScorecard = await this.client.get(`${this.url.GOLF_SCORECARD_DETAIL}`, { params: { 'scorecard-ids': scorecardId } });
if (!golfScorecard) {
throw new Error('Invalid or empty golf scorecard data response.');
}
return golfScorecard;
}
catch (error) {
throw new Error(`Error in getGolfScorecard: ${error.message}`);
}
}
async getHeartRate(date = new Date()) {
try {
const dateString = (0, DateUtils_1.toDateString)(date);
const heartRate = await this.client.get(`${this.url.DAILY_HEART_RATE}`, { params: { date: dateString } });
return heartRate;
}
catch (error) {
throw new Error(`Error in getHeartRate: ${error.message}`);
}
}
async getCourses() {
try {
const coursesForUser = await this.client.get(`${this.url.COURSE_OWNER}`);
const courses_favorite = await this.client.get(`${this.url.COURSE_FAVORITE}`);
const course = [
...coursesForUser.coursesForUser,
...courses_favorite
];
const uniqCourse = lodash_1.default.uniqBy(course, 'courseId');
return uniqCourse;
}
catch (error) {
throw new Error(`Error in getHeartRate: ${error.message}`);
}
}
async getCourse(course) {
try {
if (!course.courseId) {
throw new Error('Missing courseId');
}
const courseDetail = await this.client.get(`${this.url.COURSE(course.courseId)}`);
return courseDetail;
}
catch (error) {
throw new Error(`Error in getHeartRate: ${error.message}`);
}
}
async createCourse(course) {
try {
const createdCourse = await this.client.post(`${this.url.COURSE()}`, lodash_1.default.omit(course, [
'courseId',
// 'description',
'matchedToSegments',
'userProfilePk',
'userGroupPk',
'firstName',
'lastName',
'displayName',
'geoRoutePk',
'sourcePk',
'hasShareableEvent',
'virtualPartnerId',
'includeLaps',
'speedMeterPerSecond',
'createDate',
'updateDate',
'targetCoordinateSystem',
'originalCoordinateSystem',
'consumer',
'elevationSource',
'hasPaceBand',
'hasPowerGuide',
'favorite',
'curatedCoursePk'
]));
return createdCourse;
}
catch (error) {
throw new Error(`Error in getHeartRate: ${error.message}`);
}
}
async consenGrant() {
try {
const result = await this.client.post(`${this.url.CONSENT_GRANT}`, {
consentTypeId: 'DI_CONNECT_UPLOAD',
consentLocale: 'en-US',
consentVersion: '59'
});
return result;
}
catch (error) {
throw new Error(`Error in consenGrant: ${error.message}`);
}
}
async get(url, data) {
const response = await this.client.get(url, data);
return response;
}
async post(url, data) {
const response = await this.client.post(url, data, {});
return response;
}
async put(url, data) {
const response = await this.client.put(url, data, {});
return response;
}
}
exports.default = GarminConnect;
//# sourceMappingURL=GarminConnect.js.map