@gooin/garmin-connect-cn
Version:
Makes it simple to interface with Garmin Connect CN to get or set any data point
542 lines • 17.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Event = void 0;
const app_root_path_1 = __importDefault(require("app-root-path"));
const CFClient_1 = __importDefault(require("../common/CFClient"));
const DateUtils_1 = require("../common/DateUtils");
const urls = __importStar(require("./Urls"));
const Urls_1 = require("./Urls");
const Running_1 = __importDefault(require("./workouts/Running"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
let config = undefined;
try {
config = app_root_path_1.default.require('/garmin.config.json');
}
catch (e) {
// Do nothing
}
var Event;
(function (Event) {
Event["sessionChange"] = "sessionChange";
})(Event = exports.Event || (exports.Event = {}));
class GarminConnect {
constructor(credentials = config) {
const headers = {
origin: urls.GARMIN_SSO_ORIGIN,
nk: 'NT'
};
this.client = new CFClient_1.default(headers);
this._userHash = undefined;
if (!credentials) {
throw new Error('Missing credentials');
}
this.credentials = credentials;
this.listeners = {};
}
get userHash() {
if (!this._userHash) {
throw new Error('User not logged in');
}
return this._userHash;
}
get sessionJson() {
const cookies = this.client.serializeCookies();
return { cookies, userHash: this._userHash };
}
set sessionJson(json) {
const { cookies, userHash } = json || {};
if (cookies && userHash) {
this._userHash = userHash;
this.client.importCookies(cookies);
}
}
/**
* Add an event listener callback
* @param event
* @param callback
*/
on(event, callback) {
if (event &&
callback &&
typeof event === 'string' &&
typeof callback === 'function') {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
}
/**
* Method for triggering any event
* @param event
* @param data
*/
triggerEvent(event, data) {
const callbacks = this.listeners[event] || [];
callbacks.forEach((cb) => cb(data));
}
/**
* Add a callback to the 'sessionChange' event
* @param callback
*/
onSessionChange(callback) {
this.on(Event.sessionChange, callback);
}
/**
* Restore an old session from storage and fallback to regular login
* @param json
* @param username
* @param password
* @returns {Promise<GarminConnect>}
*/
async restoreOrLogin(json, username, password) {
return this.restore(json).catch((e) => {
console.warn(e);
return this.login(username, password);
});
}
/**
* Restore an old session from storage
* @param json
* @returns {Promise<GarminConnect>}
*/
async restore(json) {
this.sessionJson = json;
try {
const info = await this.getUserInfo();
const { displayName } = info || {};
if (displayName && displayName === this.userHash) {
// Session restoration was successful
return this;
}
throw new Error('Unable to restore session, user hash do not match');
}
catch (e) {
throw new Error(`Unable to restore session due to: ${e}`);
}
}
/**
* Login to Garmin Connect
* @param username
* @param password
* @returns {Promise<*>}
*/
async login(username, password) {
if (username && password) {
this.credentials.username = username;
this.credentials.password = password;
}
let tempCredentials = {
...this.credentials,
rememberme: 'on',
embed: 'false'
};
await this.client.get(urls.SIGNIN_URL);
await this.client.post(urls.SIGNIN_URL, tempCredentials);
const userPreferences = await this.getUserInfo();
const { displayName } = userPreferences;
this._userHash = displayName;
return this;
}
// User info
/**
* Get basic user information
* @returns {Promise<*>}
*/
async getUserInfo() {
return this.get(urls.userInfo());
}
/**
* Get social user information
* @returns {Promise<*>}
*/
async getSocialProfile() {
return this.get(urls.socialProfile(this.userHash));
}
/**
* Get a list of all social connections
* @returns {Promise<*>}
*/
async getSocialConnections() {
return this.get(urls.socialConnections(this.userHash));
}
// Devices
/**
* Get a list of all registered devices
* @returns {Promise<*>}
*/
async getDeviceInfo() {
return this.get(urls.deviceInfo(this.userHash));
}
// Sleep data
/**
* Get detailed sleep data for a specific date
* @param date
* @returns {Promise<*>}
*/
async getSleepData(date = new Date()) {
const dateString = (0, DateUtils_1.toDateString)(date);
return this.get(urls.dailySleepData(this.userHash), {
date: dateString
});
}
/**
* Get sleep data summary for a specific date
* @param date
* @returns {Promise<*>}
*/
async getSleep(date = new Date()) {
const dateString = (0, DateUtils_1.toDateString)(date);
return this.get(urls.dailySleep(), { date: dateString });
}
// Heart rate
/**
* Get heart rate measurements for a specific date
* @param date
* @returns {Promise<*>}
*/
async getHeartRate(date = new Date()) {
const dateString = (0, DateUtils_1.toDateString)(date);
return this.get(urls.dailyHeartRate(this.userHash), {
date: dateString
});
}
// Weight
/**
* Post a new body weight
* @param weight
* @returns {Promise<*>}
*/
async setBodyWeight(weight) {
if (weight) {
const roundWeight = Math.round(weight * 1000);
const data = { userData: { weight: roundWeight } };
return this.put(urls.userSettings(), data);
}
return Promise.reject();
}
// Activites
/**
* Get list of activites
* @param start
* @param limit
* @returns {Promise<*>}
*/
async getActivities(start, limit) {
return this.get(urls.activities(), { start, limit });
}
/**
* Get details about an activity
* @param activityId
* @returns {Promise<IActivityDetails>}
*/
async getActivityDetails(activityId) {
if (activityId) {
return this.get(urls.activity(activityId));
}
return Promise.reject();
}
/**
* Get metrics details about an activity
* @param activity
* @param maxChartSize
* @param maxPolylineSize
* @returns {Promise<*>}
*/
async getActivity(activity, maxChartSize, maxPolylineSize) {
const { activityId } = activity || {};
if (activityId) {
return this.get(urls.activityDetails(activityId), {
maxChartSize,
maxPolylineSize
});
}
return Promise.reject();
}
/**
* Get weather data from an activity
* @param activity
* @returns {Promise<*>}
*/
async getActivityWeather(activity) {
const { activityId } = activity || {};
if (activityId) {
return this.get(urls.weather(activityId));
}
return Promise.reject();
}
/**
* Updates an activity
* @param activity
* @returns {Promise<*>}
*/
async updateActivity(activity) {
return this.put(urls.activity(activity.activityId), activity);
}
/**
* Deletes an activity
* @param activity
* @returns {Promise<*>}
*/
async deleteActivity(activity) {
const { activityId } = activity || {};
if (activityId) {
const headers = { 'x-http-method-override': 'DELETE' };
return this.client.postJson(urls.activity(activityId), undefined, headers);
}
return Promise.reject();
}
/**
* Get list of activities in your news feed
* @param start
* @param limit
* @returns {Promise<*>}
*/
async getNewsFeed(start, limit) {
return this.get(urls.newsFeed(), { start, limit });
}
// Steps
/**
* Get step count for a specific date
* @param date
* @returns {Promise<*>}
*/
async getSteps(date = new Date()) {
const dateString = (0, DateUtils_1.toDateString)(date);
return this.get(urls.dailySummaryChart(this.userHash), {
date: dateString
});
}
// Workouts
/**
* Get list of workouts
* @param start
* @param limit
* @returns {Promise<*>}
*/
async getWorkouts(start, limit) {
return this.get(urls.workouts(), { start, limit });
}
/**
* Download original activity data to disk as zip
* Resolves to absolute path for the downloaded file
* @param activity : any
* @param dir Will default to current working directory
* @param type : string - Will default to 'zip'. Other possible values are 'tcx', 'gpx' or 'kml'.
* @returns {Promise<*>}
*/
async downloadOriginalActivityData(activity, dir, type) {
const { activityId } = activity || {};
if (activityId) {
const url = !type || type === Urls_1.ExportFileType.zip
? urls.originalFile(activityId)
: urls.exportFile(activityId, type);
return this.client.downloadBlob(dir, url);
}
return Promise.reject();
}
/**
* Uploads an activity file ('gpx', 'tcx', or 'fit')
* @param file the file to upload
* @param format the format of the file. If undefined, the extension of the file will be used.
* @returns {Promise<*>}
*/
async uploadActivity(file, format) {
var _a;
const detectedFormat = (_a = (format || path_1.default.extname(file))) === null || _a === void 0 ? void 0 : _a.toLowerCase();
const filename = path_1.default.basename(file);
if (Object.values(Urls_1.UploadFileType).includes(detectedFormat)) {
return Promise.reject();
}
const fileBuffer = fs_1.default.readFileSync(file);
const response = this.client.post(urls.upload(format), {
userfile: {
value: fileBuffer,
options: {
filename
}
}
});
return response;
}
/**
* Adds a running workout with one step of completeing a set distance.
* @param name
* @param meters
* @param description
* @returns {Promise<*>}
*/
async addRunningWorkout(name, meters, description) {
const running = new Running_1.default();
running.name = name;
running.distance = meters;
running.description = description;
return this.addWorkout(running);
}
/**
* Add a new workout preset.
* @param workout
* @returns {Promise<*>}
*/
async addWorkout(workout) {
if (workout.isValid()) {
const data = { ...workout.toJson() };
if (!data.description) {
data.description = 'Added by garmin-connect for Node.js';
}
return this.post(urls.workout(), data);
}
return Promise.reject();
}
/**
* Add a workout to your workout calendar.
* @param workout
* @param date
* @returns {Promise<*>}
*/
async scheduleWorkout(workout, date) {
const { workoutId } = workout || {};
if (workoutId && date) {
const dateString = (0, DateUtils_1.toDateString)(date);
return this.post(urls.schedule(workoutId), { date: dateString });
}
return Promise.reject();
}
/**
* Delete a workout based on a workout object.
* @param workout
* @returns {Promise<*>}
*/
async deleteWorkout(workout) {
const { workoutId } = workout || {};
if (workoutId) {
const headers = { 'x-http-method-override': 'DELETE' };
return this.client.postJson(urls.workout(workoutId), undefined, headers);
}
return Promise.reject();
}
// Badges
/**
* Get list of earned badges
* @returns {Promise<*>}
*/
async getBadgesEarned() {
return this.get(urls.badgesEarned());
}
/**
* Get list of available badges
* @returns {Promise<*>}
*/
async getBadgesAvailable() {
return this.get(urls.badgesAvailable());
}
/**
* Get details about an badge
* @param badge
* @returns {Promise<*>}
*/
async getBadge(badge) {
const { badgeId } = badge || {};
if (badgeId) {
return this.get(urls.badgeDetail(badgeId));
}
return Promise.reject();
}
/**
* Uploads an image to an activity
* @param activity
* @param file the file to upload
* @returns {Promise<*>}
*/
async uploadImage(activity, file) {
return this.client.post(urls.image(activity.activityId), {
file: {
value: fs_1.default.readFileSync(file),
options: {
filename: path_1.default.basename(file)
}
}
});
}
/**
* Delete an image from an activity
* @param activity
* @param imageId, can be found in `activityImages` array of the activity
* @returns {Promise<void>}
*/
async deleteImage(activity, imageId) {
return this.client.delete(urls.imageDelete(activity.activityId, imageId));
}
/**
* List the gear available at a certain date
* @param userProfilePk, user profile private key (can be found in user or activity details)
* @param availableGearDate, list gear available at this date only
* @returns {Promise<Gear[]>}
*/
async listGear(userProfilePk, availableGearDate) {
return this.client.get(urls.listGear(userProfilePk, availableGearDate));
}
/**
* Link gear to activity
* @param activityId, Activity ID
* @param gearUuid, UUID of the gear
* @returns {Promise<Gear>}
*/
async linkGear(activityId, gearUuid) {
return this.put(urls.linkGear(activityId, gearUuid), {});
}
/**
* Unlink gear to activity
* @param activityId, Activity ID
* @param gearUuid, UUID of the gear
* @returns {Promise<Gear>}
*/
async unlinkGear(activityId, gearUuid) {
return this.put(urls.unlinkGear(activityId, gearUuid), {});
}
// General methods
async get(url, data) {
const response = await this.client.get(url, data);
this.triggerEvent(Event.sessionChange, this.sessionJson);
return response;
}
async post(url, data) {
const response = await this.client.postJson(url, data, {});
this.triggerEvent(Event.sessionChange, this.sessionJson);
return response;
}
async put(url, data) {
const response = await this.client.putJson(url, data);
this.triggerEvent(Event.sessionChange, this.sessionJson);
return response;
}
}
exports.default = GarminConnect;
//# sourceMappingURL=GarminConnect.js.map