technogym-js 0.0.0-stage → 0.1.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/AGENTS.md +83 -0
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +147 -2
- package/dist/index.cjs +1016 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +606 -0
- package/dist/index.d.ts +606 -0
- package/dist/index.js +961 -0
- package/dist/index.js.map +1 -0
- package/package.json +93 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/auth/constants.ts","../src/auth/session-store.ts","../src/http/fetcher.ts","../src/util/dates.ts","../src/parse.ts","../src/client.ts","../src/analysis.ts","../src/technogym.ts"],"sourcesContent":["/**\n * An unofficial TypeScript client for the Technogym mywellness API.\n *\n * Exports `TechnogymClient` (sign-in, session and the HTTP transport), `Technogym` (one method\n * per endpoint), the session stores, the `TechnogymError` hierarchy, the response types, the\n * parsers behind every `get*` method, and the analytics helpers (`channelStats`,\n * `segmentAnalytics`, `windowSamples`, `downsample`).\n *\n * ```ts\n * import { FileSessionStore, Technogym, TechnogymClient } from \"technogym-js\";\n *\n * const client = new TechnogymClient({ sessionStore: new FileSessionStore(\"./session\") });\n * await client.login(email, password);\n *\n * const technogym = new Technogym(client);\n * console.log(await technogym.getRecentWorkouts(7));\n * ```\n *\n * @module\n */\nexport * from \"./errors.js\";\nexport {\n TechnogymClient,\n isAuthErrorEntry,\n type Credentials,\n type ServiceOptions,\n type TechnogymClientOptions,\n} from \"./client.js\";\nexport { Technogym, type AnalyzedExercise, type ListSessionsOptions } from \"./technogym.js\";\nexport {\n FileSessionStore,\n MemorySessionStore,\n type Session,\n type SessionStore,\n} from \"./auth/session-store.js\";\nexport { APP_ID, APP_NAME, CORE_URL, SERVICES_URL } from \"./auth/constants.js\";\nexport type * from \"./types.js\";\nexport {\n parseExercise,\n parseExerciseAnalytics,\n parseHrSession,\n parseProperties,\n parseSessionSummary,\n parseUser,\n parseWorkoutSession,\n} from \"./parse.js\";\nexport {\n CUMULATIVE_CHANNELS,\n SETPOINT_CHANNELS,\n channelStats,\n downsample,\n segmentAnalytics,\n windowSamples,\n type ChannelStats,\n type CumulativeDelta,\n type Segment,\n type SegmentBy,\n type SegmentMode,\n type SegmentOptions,\n} from \"./analysis.js\";\n","/**\n * Base class of every error this library throws, so `instanceof TechnogymError` catches them all.\n * `name` is the concrete subclass's name.\n */\nexport class TechnogymError extends Error {\n /** `options.cause` is the underlying error, if any. */\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/**\n * Login failed, there is no session, or mywellness rejected the session token (HTTP 401/403 or an\n * auth-looking error in the response envelope). Sign in again.\n */\nexport class TechnogymAuthError extends TechnogymError {}\n\n/** Network failure or timeout, after retries. */\nexport class TechnogymConnectionError extends TechnogymError {}\n\n/** 429. `retryAfter` is seconds, taken from the Retry-After header when present. */\nexport class TechnogymRateLimitError extends TechnogymError {\n /** Thrown by the transport on a 429. */\n constructor(\n message: string,\n /** Seconds to wait before retrying, from the `Retry-After` header; absent without one. */\n readonly retryAfter?: number,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\n/** Any other non-2xx response. */\nexport class TechnogymHttpError extends TechnogymError {\n /** Thrown by the transport for a non-2xx response that no other error class covers. */\n constructor(\n message: string,\n /** The HTTP status code. */\n readonly status: number,\n /** The URL that was requested, without its query string. */\n readonly url: string,\n /** The response body, as text. */\n readonly body: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\n/** One entry of the `errors` array mywellness puts in a 200 response when a call fails. */\nexport interface MywellnessErrorEntry {\n /** Machine-readable reason, e.g. `ClientApplicationNotTrusted`. */\n details?: string;\n /** Human-readable message, when there is one. */\n errorMessage?: string;\n /** Alternative message field some endpoints use. */\n message?: string;\n [key: string]: unknown;\n}\n\n/**\n * mywellness answered 200 with a non-empty `errors` array that is not about the session. `errors`\n * is that array, untouched.\n */\nexport class TechnogymApiError extends TechnogymError {\n /** Thrown when a 200 response's envelope carries errors. */\n constructor(\n message: string,\n /** The endpoint's last path segment, e.g. `ActivityHistory`. */\n readonly action: string,\n /** The envelope's `errors` array. */\n readonly errors: readonly MywellnessErrorEntry[],\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\n/** The requested session, exercise or analytics record does not exist. */\nexport class TechnogymNotFoundError extends TechnogymError {}\n","/** Account and login host. */\nexport const CORE_URL = \"https://core.mywellness.com\";\n/** Training-data host; every path under it starts with the facility's url slug. */\nexport const SERVICES_URL = \"https://services.mywellness.com\";\n\n/**\n * App id and name of the official end-user web app (endusernext.mywellness.com). Without these\n * headers the API answers `ClientApplicationNotTrusted`.\n */\nexport const APP_ID = \"EC1D38D7-D359-48D0-A60C-D8C0B8FB9DF9\";\nexport const APP_NAME = \"enduserweb\";\nexport const APP_VERSION = \"1.0\";\n\nexport const LOGIN_PATH = \"/v2/enduser/authentication/login\";\n\nexport const USER_AGENT = \"technogym-js (+https://github.com/DynamicsNinja/technogym-js)\";\n\n/** The headers every mywellness request carries. */\nexport function appHeaders(): Record<string, string> {\n return {\n accept: \"application/json\",\n \"user-agent\": USER_AGENT,\n \"x-mwapps-appid\": APP_ID,\n \"x-mwapps-client\": APP_NAME,\n \"x-mwapps-clientversion\": `${APP_VERSION},${APP_NAME}`,\n };\n}\n","import { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Facility, UserInfo } from \"../types.js\";\n\n/** A signed-in mywellness session: the bearer token and the account it belongs to. */\nexport interface Session {\n /** The bearer token sent with every request. */\n token: string;\n /** The account, as the login response described it. */\n user: UserInfo;\n /** When the session was created or its token last rotated, in epoch milliseconds. */\n savedAt: number;\n}\n\n/**\n * Persists a client's session between runs. Implement it to keep sessions in a database or a\n * secret store; `FileSessionStore` and `MemorySessionStore` are ready-made.\n */\nexport interface SessionStore {\n /** The saved session, or `null` when there is none. */\n load(): Promise<Session | null>;\n /** Saves a session, replacing any saved before. */\n save(session: Session): Promise<void>;\n /** Removes the saved session. */\n clear(): Promise<void>;\n}\n\n/** Keeps the session in memory only, so it is lost when the process exits. The client's default. */\nexport class MemorySessionStore implements SessionStore {\n #session: Session | null = null;\n\n async load(): Promise<Session | null> {\n return this.#session;\n }\n async save(session: Session): Promise<void> {\n this.#session = session;\n }\n async clear(): Promise<void> {\n this.#session = null;\n }\n}\n\nconst SESSION_FILE = \"session.json\";\n\n/** Stores the session as `session.json` (mode 0600) in `dir`, which is created on the first save. */\nexport class FileSessionStore implements SessionStore {\n constructor(private readonly dir: string) {}\n\n async load(): Promise<Session | null> {\n try {\n return parseSession(JSON.parse(await readFile(join(this.dir, SESSION_FILE), \"utf8\")));\n } catch {\n return null;\n }\n }\n\n async save(session: Session): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n // Unique per call so two processes sharing the directory never write the same temp file.\n const tmp = join(this.dir, `${SESSION_FILE}.${process.pid}-${Math.random().toString(36).slice(2)}.tmp`);\n try {\n await writeFile(tmp, JSON.stringify(session, null, 2), { mode: 0o600 });\n await rename(tmp, join(this.dir, SESSION_FILE));\n } catch (err) {\n await rm(tmp, { force: true });\n throw err;\n }\n }\n\n async clear(): Promise<void> {\n await rm(join(this.dir, SESSION_FILE), { force: true });\n }\n}\n\n/**\n * Validates a stored session's shape. A file missing the token, the user id or the facility list\n * is rejected whole, so `loadSession()` returns `false` and the caller signs in again rather than\n * carrying a half-valid session around.\n */\nfunction parseSession(value: unknown): Session | null {\n if (!isRecord(value) || typeof value[\"token\"] !== \"string\" || !value[\"token\"]) return null;\n const user = value[\"user\"];\n if (!isRecord(user) || typeof user[\"userId\"] !== \"string\" || !user[\"userId\"]) return null;\n if (!Array.isArray(user[\"facilities\"])) return null;\n const facilities: Facility[] = [];\n for (const f of user[\"facilities\"] as unknown[]) {\n if (!isRecord(f) || typeof f[\"url\"] !== \"string\") return null;\n const str = (v: unknown) => (typeof v === \"string\" ? v : \"\");\n facilities.push({ id: str(f[\"id\"]), url: f[\"url\"], name: str(f[\"name\"]) });\n }\n const text = (key: string) => (typeof user[key] === \"string\" ? user[key] : \"\");\n return {\n token: value[\"token\"],\n user: {\n userId: user[\"userId\"],\n firstName: text(\"firstName\"),\n lastName: text(\"lastName\"),\n nickName: text(\"nickName\"),\n measurementSystem: text(\"measurementSystem\"),\n culture: text(\"culture\"),\n facilities,\n },\n savedAt: typeof value[\"savedAt\"] === \"number\" ? value[\"savedAt\"] : 0,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import {\n TechnogymAuthError,\n TechnogymConnectionError,\n TechnogymHttpError,\n TechnogymRateLimitError,\n} from \"../errors.js\";\n\nconst RETRY_STATUSES = new Set([408, 500, 502, 503, 504]);\n\n/**\n * Methods safe to retry automatically. mywellness reads are POSTs, so `TechnogymClient` opts each\n * read in with `RequestOptions.retry`; the login POST is never retried.\n */\nconst IDEMPOTENT_METHODS = new Set([\"GET\", \"HEAD\", \"PUT\", \"DELETE\", \"OPTIONS\"]);\n\nexport interface FetcherOptions {\n timeoutMs?: number;\n retries?: number;\n backoffMs?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport interface RequestOptions {\n method?: string;\n json?: unknown;\n /**\n * Callers that must GUARANTEE a header wins (the bearer token) build a `Headers` and `.set()` it:\n * `Headers` is case-insensitive, object spread is not.\n */\n headers?: RequestInit[\"headers\"];\n /** Per-request timeout, overriding the Fetcher's default. */\n timeoutMs?: number;\n /** Opt in to retrying a non-idempotent method (POST) on a retryable status or network error. */\n retry?: boolean;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport class Fetcher {\n readonly #timeoutMs: number;\n readonly #retries: number;\n readonly #backoffMs: number;\n readonly #fetch: typeof fetch;\n\n constructor(options: FetcherOptions = {}) {\n this.#timeoutMs = options.timeoutMs ?? 30_000;\n this.#retries = options.retries ?? 3;\n this.#backoffMs = options.backoffMs ?? 500;\n this.#fetch = options.fetchImpl ?? globalThis.fetch;\n }\n\n async request(url: string, options: RequestOptions = {}): Promise<Response> {\n const safeUrl = redactUrl(url);\n const headers = new Headers(options.headers ?? {});\n let body: string | undefined;\n if (options.json !== undefined) {\n body = JSON.stringify(options.json);\n if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"application/json\");\n }\n\n const method = options.method ?? \"GET\";\n const canRetry = IDEMPOTENT_METHODS.has(method.toUpperCase()) || options.retry === true;\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n\n let lastError: unknown;\n for (let attempt = 0; attempt <= this.#retries; attempt++) {\n if (attempt > 0) await sleep(this.#backoffMs * 2 ** (attempt - 1));\n\n let res: Response;\n try {\n res = await this.#fetch(url, {\n method,\n headers,\n body,\n redirect: \"follow\",\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n lastError = cause;\n if (canRetry && attempt < this.#retries) continue;\n throw new TechnogymConnectionError(\n `Request to ${safeUrl} failed after ${attempt + 1} attempt${attempt === 0 ? \"\" : \"s\"}`,\n { cause: lastError },\n );\n }\n\n if (res.ok) return res;\n if (RETRY_STATUSES.has(res.status) && canRetry && attempt < this.#retries) continue;\n throw await toError(res, safeUrl);\n }\n\n throw new TechnogymConnectionError(`Request to ${safeUrl} failed after ${this.#retries + 1} attempts`, {\n cause: lastError,\n });\n }\n}\n\nasync function toError(res: Response, safeUrl: string): Promise<Error> {\n const body = await res.text().catch(() => \"\");\n if (res.status === 429) {\n const raw = res.headers.get(\"retry-after\");\n const retryAfter = raw && !Number.isNaN(Number(raw)) ? Number(raw) : undefined;\n return new TechnogymRateLimitError(`Rate limited by mywellness at ${safeUrl}`, retryAfter);\n }\n if (res.status === 401 || res.status === 403) {\n return new TechnogymAuthError(`Not authorized for ${safeUrl} (${res.status})`);\n }\n return new TechnogymHttpError(`Request to ${safeUrl} failed with ${res.status}`, res.status, safeUrl, body);\n}\n\n/** Drops the query string, so an error message can never carry one. */\nfunction redactUrl(url: string): string {\n try {\n const u = new URL(url);\n return `${u.origin}${u.pathname}`;\n } catch {\n return url;\n }\n}\n","import { TechnogymError } from \"../errors.js\";\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * Normalises a `YYYY-MM-DD` string or a `Date` to `YYYY-MM-DD`. A `Date` uses its LOCAL calendar\n * day, because sessions are partitioned by the gym's day and \"today\" means the caller's today.\n */\nexport function toIsoDate(value: string | Date): string {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) throw new TechnogymError(\"Invalid Date\");\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;\n }\n const m = ISO_DATE.exec(value);\n if (!m) throw new TechnogymError(`Invalid date \"${value}\"; expected YYYY-MM-DD`);\n // Date.UTC rolls 2026-13-01 over to 2027-01-01; a round trip that changes the text is invalid.\n const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));\n if (d.toISOString().slice(0, 10) !== value) throw new TechnogymError(`Invalid date \"${value}\"`);\n return value;\n}\n\n/** `YYYY-MM-DD` to the API's `yyyyMMdd` partition key. */\nexport function toPartitionKey(value: string | Date): string {\n return toIsoDate(value).replaceAll(\"-\", \"\");\n}\n\n/** The API's `yyyyMMdd` partition key to `YYYY-MM-DD`; anything else is returned as text. */\nexport function fromPartitionKey(value: unknown): string {\n const s = typeof value === \"string\" || typeof value === \"number\" ? String(value) : \"\";\n return /^\\d{8}$/.test(s) ? `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}` : s;\n}\n\n/** `date` moved by `days` calendar days, as `YYYY-MM-DD`. */\nexport function addDays(date: string, days: number): string {\n const [y, m, d] = date.split(\"-\").map(Number) as [number, number, number];\n const t = new Date(Date.UTC(y, m - 1, d + days));\n return t.toISOString().slice(0, 10);\n}\n","import type {\n AnalyticsSample,\n Exercise,\n ExerciseAnalytics,\n Facility,\n HrSession,\n HrZone,\n Lap,\n PropertyMap,\n SessionSummary,\n SetData,\n UserInfo,\n WorkloadRange,\n WorkoutSession,\n} from \"./types.js\";\nimport { fromPartitionKey } from \"./util/dates.js\";\n\n/**\n * Pure parsers from mywellness payloads to the types in `types.ts`. They never throw on a\n * missing field: absent strings become `\"\"`, absent numbers `null`, absent lists `[]`.\n */\n\ntype Rec = Record<string, unknown>;\n\nfunction rec(value: unknown): Rec {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as Rec) : {};\n}\n\nfunction list(value: unknown): Rec[] {\n return Array.isArray(value) ? (value as unknown[]).map(rec) : [];\n}\n\nfunction text(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n return typeof value === \"object\" && value !== null ? JSON.stringify(value) : \"\";\n}\n\n/** The first non-empty value, as text. */\nfunction firstText(...values: unknown[]): string {\n for (const v of values) if (v != null && v !== \"\") return text(v);\n return \"\";\n}\n\nexport function intOrNull(value: unknown): number | null {\n if (value == null || value === \"\") return null;\n const n = Number(value);\n return Number.isFinite(n) ? Math.trunc(n) : null;\n}\n\nfunction numOrNull(value: unknown): number | null {\n if (value == null || value === \"\") return null;\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\n/** The login response's account, or `null` when it has no `userContext.id`. */\nexport function parseUser(data: unknown): UserInfo | null {\n const d = rec(data);\n const uc = rec(d[\"userContext\"]);\n if (!uc[\"id\"]) return null;\n const facilities: Facility[] = list(d[\"facilities\"]).map((f) => ({\n id: text(f[\"id\"]),\n url: text(f[\"url\"]),\n name: text(f[\"name\"]),\n }));\n return {\n userId: text(uc[\"id\"]),\n firstName: text(uc[\"firstName\"]),\n lastName: text(uc[\"lastName\"]),\n nickName: text(uc[\"nickName\"]),\n measurementSystem: text(uc[\"measurementSystem\"]),\n culture: text(uc[\"defaultCulture\"]),\n facilities,\n };\n}\n\n/** One `ActivityHistory` item. */\nexport function parseSessionSummary(item: unknown): SessionSummary {\n const i = rec(item);\n const pad = (v: unknown) => String(intOrNull(v) ?? 0).padStart(2, \"0\");\n return {\n sessionId: text(i[\"idCr\"]),\n date: fromPartitionKey(i[\"partitionDate\"]),\n time: `${pad(i[\"hour\"])}:${pad(i[\"minute\"])}`,\n name: text(i[\"name\"]),\n type: text(i[\"type\"]),\n durationMin: intOrNull(i[\"totalDurationInMinutes\"] ?? i[\"duration\"]),\n moves: intOrNull(i[\"moves\"]),\n calories: intOrNull(i[\"calories\"]),\n exercisesDone: intOrNull(i[\"numberOfExerciseDoneWithGroups\"]),\n exercisesPlanned: intOrNull(i[\"numberOfExerciseToDoWithGroups\"]),\n userWorkoutSessionId: text(i[\"userWorkoutSessionId\"]),\n facilityId: text(i[\"facilityId\"]),\n workoutEffectiveness: intOrNull(i[\"workoutEffectiveness\"]),\n workoutCompliance: intOrNull(i[\"workoutCompliance\"]),\n };\n}\n\n/**\n * Normalises mywellness property rows. Two shapes exist: session and HR rows carry `value` in the\n * base unit plus `displayValue` in `unitOfMeasureShortString`; CardioLog rows carry `rawValue`\n * already in the display unit (`uM`) and `value` as a formatted string.\n */\nexport function parseProperties(rows: unknown): PropertyMap {\n const out: PropertyMap = {};\n for (const p of list(rows)) {\n const key = text(p[\"physicalProperty\"] ?? p[\"property\"]);\n if (!key) continue;\n if (\"rawValue\" in p) {\n out[key] = { value: p[\"rawValue\"], displayValue: p[\"rawValue\"], unit: text(p[\"uM\"]), formatted: p[\"value\"] };\n } else {\n out[key] = {\n value: p[\"value\"],\n displayValue: \"displayValue\" in p ? p[\"displayValue\"] : p[\"value\"],\n unit: text(p[\"unitOfMeasureShortString\"]),\n formatted: p[\"formattedValue\"],\n };\n }\n }\n return out;\n}\n\nfunction stepToSet(setNumber: number, properties: PropertyMap): SetData {\n const num = (key: string) => numOrNull(properties[key]?.value);\n return {\n setNumber,\n reps: num(\"IsoReps\") ?? num(\"Reps\"),\n weightKg: num(\"IsoWeight\") ?? num(\"Weight\"),\n restSec: num(\"RestTime\"),\n durationSec: num(\"Duration\"),\n properties,\n };\n}\n\n/** One `physicalActivities` entry of a session. */\nexport function parseExercise(activity: unknown): Exercise {\n const act = rec(activity);\n const pa = rec(act[\"performedPhysicalActivity\"]);\n const pdata = rec(pa[\"data\"]);\n const prescribed = rec(act[\"displayPrescibedPhysicalActivity\"]); // sic: the API's spelling\n\n const workloadRanges: Record<string, WorkloadRange> = {};\n for (const c of list(pdata[\"constraints\"])) {\n const prop = text(c[\"physicalProperty\"]);\n if (!prop) continue;\n const range = rec(c[\"discreteRange\"]);\n workloadRanges[prop] = {\n min: range[\"min\"],\n max: range[\"max\"],\n step: range[\"step\"],\n readOnly: Boolean(c[\"isReadOnly\"]),\n visibility: c[\"visibility\"],\n };\n }\n\n const extData: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(rec(pa[\"extData\"]))) {\n if (v != null && v !== \"\") extData[k.startsWith(\"mwc_\") ? k.slice(4) : k] = v;\n }\n\n return {\n activityId: text(pa[\"id\"]),\n analyticsId: firstText(act[\"analyticsId\"], pa[\"analiticsId\"]), // sic\n name: text(act[\"physicalActivityName\"]),\n machine: text(act[\"equipmentName\"]),\n equipmentType: text(act[\"equipmentType\"]),\n type: text(act[\"physicalActivityType\"]),\n isCardio: Boolean(act[\"isCardio\"]),\n status: text(act[\"status\"]),\n doneOn: text(pa[\"doneOn\"]),\n position: intOrNull(act[\"position\"]),\n duration: firstText(act[\"displayDurationDoneShort\"], act[\"displayDurationShort\"]),\n calories: text(act[\"displayCaloriesDoneShort\"]),\n moves: text(act[\"displayMoveDoneShort\"]),\n muscles: list(act[\"muscles\"]).map((m) => firstText(m[\"muscleName\"], m[\"muscleType\"])),\n summary: parseProperties(pdata[\"data\"]),\n sets: list(pdata[\"steps\"]).map((step, i) => stepToSet(i + 1, parseProperties(step[\"data\"]))),\n prescribedSets: list(prescribed[\"steps\"]).map((step, i) => stepToSet(i + 1, parseProperties(step[\"properties\"]))),\n shortName: firstText(act[\"physicalActivityShortName\"], pa[\"shortName\"]),\n activityCode: intOrNull(act[\"physicalActivityCode\"]),\n activityTypeId: text(act[\"physicalActivityId\"]),\n equipmentCode: intOrNull(act[\"equipmentCode\"]),\n targetType: text(act[\"targetType\"]),\n doneAs: text(pa[\"doneAs\"]),\n manuallyDone: Boolean(pa[\"manuallyDone\"]),\n hasAdjustments: Boolean(act[\"hasAdjustments\"]),\n donePartially: Boolean(act[\"hasBeenDonePartially\"]),\n progress: intOrNull(pa[\"progress\"] ?? act[\"progress\"]),\n estimated: {\n duration: act[\"estimatedDuration\"],\n calories: act[\"estimatedCalories\"],\n moves: act[\"estimatedMove\"],\n },\n workloadRanges,\n extData,\n };\n}\n\n/** A `GetPerformedWorkoutSessionByIdCr` payload. Exercises never performed are left out. */\nexport function parseWorkoutSession(data: unknown, sessionId: string): WorkoutSession {\n const d = rec(data);\n const startedOn = text(d[\"startedOn\"]);\n const done = parseProperties(d[\"doneSummary\"]);\n const muscleScores: Record<string, number> = {};\n for (const m of list(d[\"muscleScores\"])) {\n muscleScores[firstText(m[\"muscleName\"], m[\"muscleType\"])] = intOrNull(m[\"score\"]) ?? 0;\n }\n return {\n sessionId,\n date: startedOn.slice(0, 10),\n startedOn,\n closedOn: text(d[\"closedOn\"]),\n name: firstText(d[\"displayName\"], d[\"name\"]),\n type: text(d[\"sessionType\"]),\n status: text(d[\"workoutSessionStatus\"]),\n duration: text(d[\"displayDurationDoneShort\"]),\n durationPlanned: text(d[\"displayDurationShort\"]),\n calories: firstText(done[\"Calories\"]?.formatted, d[\"displayCaloriesDoneShort\"]),\n moves: firstText(done[\"Move\"]?.formatted, d[\"displayMoveDoneShort\"]),\n efficacy: intOrNull(d[\"workoutEfficacy\"]),\n efficacyQuality: text(d[\"workoutEfficacyQuality\"]),\n efficacyDescription: text(d[\"workoutEfficacyDescription\"]),\n muscleScores,\n exercises: list(d[\"physicalActivities\"])\n .filter((a) => rec(a[\"performedPhysicalActivity\"])[\"id\"])\n .map(parseExercise),\n fromTgCoach: Boolean(d[\"isFromTgCoach\"]),\n facilityId: text(d[\"startedInFacilityId\"]),\n userWorkoutSessionId: text(d[\"userWorkoutSessionId\"]),\n clientApplication: text(rec(d[\"extData\"])[\"mwc_client_application\"]),\n doneSummary: done,\n };\n}\n\nfunction parseZones(rows: unknown): HrZone[] {\n return list(rows).map((z) => ({\n name: text(z[\"name\"]),\n lowerBound: numOrNull(z[\"lowerBound\"]),\n upperBound: numOrNull(z[\"upperBound\"]),\n secondsInZone: numOrNull(z[\"secondsInZone\"]) ?? 0,\n }));\n}\n\n/**\n * A `CardioLog/{id}/Details` payload. The API spells the block `analitics`; `analytics` is\n * accepted too. Samples with an empty value vector are dropped.\n */\nexport function parseExerciseAnalytics(data: unknown, analyticsId: string): ExerciseAnalytics {\n const d = rec(data);\n const an = rec(d[\"analitics\"] ?? d[\"analytics\"]);\n\n const channelByIndex = new Map<number, string>();\n for (const ch of list(an[\"descriptor\"])) {\n const index = intOrNull(ch[\"i\"]);\n if (index === null) continue;\n channelByIndex.set(index, text(rec(ch[\"pr\"])[\"name\"]));\n }\n const channels = [...channelByIndex.entries()].sort(([a], [b]) => a - b).map(([, name]) => name);\n\n const samples: AnalyticsSample[] = [];\n for (const raw of list(an[\"samples\"])) {\n const vs = Array.isArray(raw[\"vs\"]) ? (raw[\"vs\"] as unknown[]) : [];\n if (vs.length === 0) continue;\n const row: AnalyticsSample = { t: intOrNull(raw[\"t\"]) ?? 0 };\n for (const [index, name] of channelByIndex) row[name] = numOrNull(vs[index]);\n samples.push(row);\n }\n\n const hr = list(an[\"hr\"])\n .filter((h) => \"hr\" in h)\n .map((h) => ({ t: intOrNull(h[\"t\"]) ?? 0, hr: intOrNull(h[\"hr\"]) ?? 0 }));\n\n const laps: Lap[] = list(an[\"laps\"]).map((lap) => ({\n n: intOrNull(lap[\"n\"]),\n lapDistanceM: numOrNull(lap[\"lapDistance\"]),\n lapTimeSec: numOrNull(lap[\"lapTime\"]),\n totalDistanceM: numOrNull(lap[\"totalDistance\"]),\n totalTimeSec: numOrNull(lap[\"totalTime\"]),\n }));\n\n return {\n analyticsId,\n name: text(d[\"name\"]),\n equipmentType: text(d[\"equipmentType\"]),\n channels,\n samples,\n hr,\n hrZones: parseZones(an[\"hrZones\"]),\n summary: parseProperties(d[\"data\"]),\n date: text(d[\"date\"]),\n target: text(d[\"target\"]),\n activityTypeId: text(d[\"physicalActivityId\"]),\n activityName: text(d[\"physicalActivityName\"]),\n laps,\n };\n}\n\n/** A `GetHrSession` payload. */\nexport function parseHrSession(data: unknown, sessionId: string, date: string): HrSession {\n const d = rec(data);\n return {\n sessionId,\n date,\n summary: parseProperties(d[\"properties\"]),\n hrSamples: list(d[\"hrSamples\"]).map((h) => ({ timestamp: text(h[\"timestamp\"]), hr: intOrNull(h[\"hr\"]) ?? 0 })),\n hrZones: parseZones(d[\"hrZones\"]),\n };\n}\n","import { appHeaders, CORE_URL, LOGIN_PATH, SERVICES_URL } from \"./auth/constants.js\";\nimport { MemorySessionStore, type Session, type SessionStore } from \"./auth/session-store.js\";\nimport {\n TechnogymApiError,\n TechnogymAuthError,\n TechnogymError,\n type MywellnessErrorEntry,\n} from \"./errors.js\";\nimport { Fetcher } from \"./http/fetcher.js\";\nimport { parseUser } from \"./parse.js\";\nimport type { Facility, UserInfo } from \"./types.js\";\n\n/** An email and password for mywellness. */\nexport interface Credentials {\n email: string;\n password: string;\n}\n\n/** Options for `new TechnogymClient(...)`. Every field is optional. */\nexport interface TechnogymClientOptions {\n /** Where the session is loaded from and saved to. Default: a `MemorySessionStore`. */\n sessionStore?: SessionStore;\n /**\n * Sign in lazily on the first call and again whenever mywellness rejects the token. Without\n * them, call `login` or `loadSession` first, and a rejected token throws `TechnogymAuthError`.\n * Credentials passed to `login()` are also kept (in memory only) for the same purpose.\n */\n credentials?: Credentials;\n /**\n * Which facility's url prefixes training paths, matched against a facility's `id` or `url`.\n * Default: the account's first facility.\n */\n facility?: string;\n /** Per-request timeout in ms. Default 30000. */\n timeoutMs?: number;\n /** How many times a transient failure of a read is retried. Default 3. */\n retries?: number;\n /** Delay before the first retry in ms, doubled on each retry after it. Default 500. */\n backoffMs?: number;\n /** A `fetch` to use instead of the global one, e.g. a mock in tests. */\n fetchImpl?: typeof fetch;\n}\n\n/** Options for one raw `services` call. */\nexport interface ServiceOptions {\n /** Per-request timeout, overriding the client's default. */\n timeoutMs?: number;\n /**\n * Retry on a 5xx or network error. Default `true`: every mywellness read is a POST, and this\n * library only reads. Pass `false` for a POST that changes something.\n */\n retry?: boolean;\n}\n\n/** mywellness `details` values that mean \"the session is gone\", not \"the call is wrong\". */\nconst AUTH_ERROR_DETAILS = new Set([\n \"TokenNotValid\",\n \"InvalidToken\",\n \"TokenExpired\",\n \"NotAuthenticated\",\n \"Unauthorized\",\n \"UserNotLogged\",\n]);\n\n/** Whether an envelope's `errors` describe a rejected session. */\nexport function isAuthErrorEntry(errors: readonly MywellnessErrorEntry[]): boolean {\n return errors.some((e) => {\n const details = String(e.details ?? e.message ?? \"\");\n return AUTH_ERROR_DETAILS.has(details) || details.toLowerCase().includes(\"token\");\n });\n}\n\nfunction errorsText(errors: readonly MywellnessErrorEntry[]): string {\n return errors.map((e) => String(e.errorMessage ?? e.message ?? e.details ?? JSON.stringify(e))).join(\"; \");\n}\n\ninterface Envelope {\n data?: unknown;\n errors?: MywellnessErrorEntry[];\n token?: unknown;\n [key: string]: unknown;\n}\n\n/**\n * The authenticated HTTP transport: signs in to mywellness, keeps the session, and sends\n * requests to `services.mywellness.com/{facility}`. Wrap it in a `Technogym` to call the API.\n */\nexport class TechnogymClient {\n /** Where this client loads and saves its session. */\n readonly sessionStore: SessionStore;\n\n readonly #fetcher: Fetcher;\n readonly #facility: string | undefined;\n #credentials: Credentials | undefined;\n #session: Session | null = null;\n #signingIn: Promise<Session> | null = null;\n\n /** Creates a client that is not signed in yet. */\n constructor(options: TechnogymClientOptions = {}) {\n this.sessionStore = options.sessionStore ?? new MemorySessionStore();\n this.#credentials = options.credentials;\n this.#facility = options.facility;\n this.#fetcher = new Fetcher({\n timeoutMs: options.timeoutMs,\n retries: options.retries,\n backoffMs: options.backoffMs,\n fetchImpl: options.fetchImpl,\n });\n }\n\n /**\n * Signs in with email and password and saves the session to the store. The credentials are\n * kept in memory so a rejected token can be renewed without asking again. Throws\n * `TechnogymAuthError` when mywellness refuses them.\n */\n async login(email: string, password: string): Promise<Session> {\n this.#credentials = { email, password };\n return this.#signIn();\n }\n\n /** Loads the saved session. Returns false when the store holds none. */\n async loadSession(): Promise<boolean> {\n const session = await this.sessionStore.load();\n if (!session) return false;\n this.#session = session;\n return true;\n }\n\n /** Uses this session from now on. Does not save it to the store. */\n setSession(session: Session): void {\n this.#session = session;\n }\n\n /** The session currently held in memory, or `null` before a login or `loadSession()`. */\n getSession(): Session | null {\n return this.#session;\n }\n\n /** Forgets the session and the in-memory credentials, and clears the store. */\n async logout(): Promise<void> {\n this.#session = null;\n this.#credentials = undefined;\n await this.sessionStore.clear();\n }\n\n /**\n * The signed-in account. Signs in first when the client was given credentials and holds no\n * session yet; otherwise throws `TechnogymAuthError`.\n */\n async user(): Promise<UserInfo> {\n return (await this.#requireSession()).user;\n }\n\n /**\n * The facility training paths are sent to (see `TechnogymClientOptions.facility`), or `null` for\n * an account with no facility. Live on 2026-10-31, an account whose login listed no facilities\n * got the same answers with no facility prefix as with any prefix, so paths go out unprefixed.\n */\n facilityOf(user: UserInfo): Facility | null {\n if (!this.#facility) return user.facilities[0] ?? null;\n const facility = user.facilities.find((f) => f.id === this.#facility || f.url === this.#facility);\n if (!facility) throw new TechnogymError(`The account has no facility \"${this.#facility}\"`);\n return facility;\n }\n\n async #requireSession(): Promise<Session> {\n if (this.#session) return this.#session;\n if (this.#credentials) return this.#signIn();\n throw new TechnogymAuthError(\"Not signed in: call login() or loadSession() first\");\n }\n\n /** Concurrent sign-ins collapse into one login request. */\n #signIn(): Promise<Session> {\n const credentials = this.#credentials;\n if (!credentials) return Promise.reject(new TechnogymAuthError(\"No credentials to sign in with\"));\n this.#signingIn ??= (async () => {\n try {\n const session = await this.#loginRequest(credentials);\n this.#session = session;\n await this.sessionStore.save(session);\n return session;\n } finally {\n // Reset on success and failure alike, so a failed login never wedges the client.\n this.#signingIn = null;\n }\n })();\n return this.#signingIn;\n }\n\n async #loginRequest({ email, password }: Credentials): Promise<Session> {\n let body: Envelope;\n try {\n const res = await this.#fetcher.request(`${CORE_URL}${LOGIN_PATH}`, {\n method: \"POST\",\n json: { username: email, password, keepMeLoggedIn: true },\n headers: appHeaders(),\n });\n body = await parseEnvelope(res);\n } catch (cause) {\n if (cause instanceof TechnogymAuthError) {\n throw new TechnogymAuthError(`Login failed: ${cause.message}`, { cause });\n }\n throw cause;\n }\n if (body.errors?.length) throw new TechnogymAuthError(`Login failed: ${errorsText(body.errors)}`);\n const data = (body.data ?? body) as Record<string, unknown>;\n const token = data[\"token\"];\n const user = parseUser(data);\n if (typeof token !== \"string\" || !token || !user) {\n throw new TechnogymAuthError(`Login failed: unexpected response (result=${JSON.stringify(data[\"result\"])})`);\n }\n return { token, user, savedAt: Date.now() };\n }\n\n /**\n * POSTs `body` to a training endpoint and returns the envelope's `data` (or the whole body when\n * it has none). `path` is relative to the facility, e.g. `/Training/User/{userId}/MyMovergy`;\n * `{userId}` is replaced with the signed-in user's id. For endpoints `Technogym` has no method for.\n *\n * A rejected session is renewed once, when the client has credentials, and the call repeated.\n */\n async services<T = unknown>(path: string, body: unknown = {}, options: ServiceOptions = {}): Promise<T | null> {\n const session = await this.#requireSession();\n try {\n return await this.#servicesOnce<T>(session, path, body, options);\n } catch (error) {\n if (!(error instanceof TechnogymAuthError) || !this.#credentials) throw error;\n // Another call may already have renewed the token while this one was in flight.\n const renewed = this.#session && this.#session.token !== session.token ? this.#session : await this.#signIn();\n return this.#servicesOnce<T>(renewed, path, body, options);\n }\n }\n\n async #servicesOnce<T>(session: Session, path: string, body: unknown, options: ServiceOptions): Promise<T | null> {\n const facility = this.facilityOf(session.user);\n const resolved = path.replaceAll(\"{userId}\", encodeURIComponent(session.user.userId));\n const headers = new Headers(appHeaders());\n headers.set(\"authorization\", `Bearer ${session.token}`);\n const prefix = facility?.url ? `/${encodeURIComponent(facility.url)}` : \"\";\n const res = await this.#fetcher.request(`${SERVICES_URL}${prefix}${resolved}`, {\n method: \"POST\",\n json: body,\n headers,\n timeoutMs: options.timeoutMs,\n retry: options.retry ?? true,\n });\n const envelope = await parseEnvelope(res);\n const action = resolved.split(\"/\").pop() ?? resolved;\n if (envelope.errors?.length) {\n if (isAuthErrorEntry(envelope.errors)) {\n throw new TechnogymAuthError(`${action}: mywellness rejected the session (${errorsText(envelope.errors)})`);\n }\n throw new TechnogymApiError(`${action}: ${errorsText(envelope.errors)}`, action, envelope.errors);\n }\n await this.#adoptRotatedToken(session, envelope.token);\n return (\"data\" in envelope ? envelope.data : envelope) as T | null;\n }\n\n /** Some responses carry a fresh `token`; keep it so the session outlives the old one. */\n async #adoptRotatedToken(session: Session, token: unknown): Promise<void> {\n if (typeof token !== \"string\" || !token || token === session.token) return;\n if (this.#session !== session) return; // a newer session already replaced this one\n this.#session = { ...session, token, savedAt: Date.now() };\n await this.sessionStore.save(this.#session);\n }\n}\n\n/** Parses a JSON response body; a non-JSON 200 becomes a `TechnogymError`, never a bare SyntaxError. */\nasync function parseEnvelope(res: Response): Promise<Envelope> {\n const text = await res.text();\n if (text.length === 0) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (cause) {\n throw new TechnogymError(\"mywellness returned a non-JSON response\", { cause });\n }\n return typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed) ? (parsed as Envelope) : { data: parsed };\n}\n","import type { AnalyticsSample, Lap } from \"./types.js\";\n\n/**\n * Derived views over `ExerciseAnalytics.samples`: per-channel stats and interval / lap / window\n * segments. Pure functions, no HTTP. The mywellness summary block omits most machine channels\n * (RunningCadence and RunningPower on a Skillrun, for one), so these fill that gap.\n */\n\n/** Channels that accumulate over the exercise: only the final value means anything, not the average. */\nexport const CUMULATIVE_CHANNELS: ReadonlySet<string> = new Set([\"HDistance\", \"Distance\", \"VDistance\", \"Calories\", \"Move\"]);\n\n/**\n * Channels SET on the console rather than measured. A change in any of them starts a new interval\n * on a treadmill or bike programme.\n */\nexport const SETPOINT_CHANNELS = [\"Speed\", \"Grade\", \"Level\", \"Resistance\", \"Incline\", \"Rpm_Target\"] as const;\n\n/** Setpoint blocks shorter than this are ramps and are folded into the interval they lead into. */\nconst MIN_INTERVAL_SEC = 10;\n\n/**\n * Aggregates of one channel. Measured channels get `avg` and `minNonzero` over the non-zero\n * readings (the machine reports 0 while the belt is stopped); cumulative channels get `final`.\n */\nexport interface ChannelStats {\n max: number;\n samples: number;\n avg?: number;\n minNonzero?: number;\n final?: number;\n}\n\n/** A cumulative channel within one segment: the amount covered in it, and its value at the end. */\nexport interface CumulativeDelta {\n delta: number;\n end: number;\n}\n\nexport type SegmentMode = \"workload\" | \"laps\" | \"fixed\";\n/** `auto`: workload intervals when there are at least two, else laps, else fixed windows. */\nexport type SegmentBy = SegmentMode | \"auto\";\n\nexport interface Segment {\n startSec: number;\n endSec: number;\n durationSec: number;\n /** 1-based, on `workload` segments. */\n interval?: number;\n /** The constant setpoint values of a `workload` segment. */\n setpoint?: Record<string, number>;\n /** On `laps` segments. */\n lap?: number | null;\n lapDistanceM?: number | null;\n /** From the average `Speed` (km/h), when there is one. */\n paceMinPerKm?: number;\n channels: Record<string, ChannelStats | CumulativeDelta>;\n}\n\nexport interface SegmentOptions {\n /** Default `auto`. */\n segmentBy?: SegmentBy;\n /** Window length for `fixed`, at least 10. Default 300. */\n segmentSeconds?: number;\n}\n\nconst round = (n: number, digits: number) => Math.round(n * 10 ** digits) / 10 ** digits;\n\nfunction numbers(samples: readonly AnalyticsSample[], channel: string): number[] {\n const out: number[] = [];\n for (const row of samples) {\n const v = row[channel];\n if (typeof v === \"number\") out.push(v);\n }\n return out;\n}\n\n/** Per-channel aggregates over `samples`. Channels with no numeric reading are left out. */\nexport function channelStats(\n samples: readonly AnalyticsSample[],\n channels: readonly string[],\n): Record<string, ChannelStats> {\n const stats: Record<string, ChannelStats> = {};\n for (const ch of channels) {\n const values = numbers(samples, ch);\n if (values.length === 0) continue;\n const entry: ChannelStats = { max: Math.max(...values), samples: values.length };\n if (CUMULATIVE_CHANNELS.has(ch)) {\n entry.final = values[values.length - 1];\n } else {\n const nonzero = values.filter((v) => v > 0);\n entry.avg = nonzero.length ? round(nonzero.reduce((a, b) => a + b, 0) / nonzero.length, 1) : 0;\n entry.minNonzero = nonzero.length ? Math.min(...nonzero) : 0;\n }\n stats[ch] = entry;\n }\n return stats;\n}\n\ntype Bound = [start: number, end: number, meta: Partial<Segment>];\n\n/**\n * Maximal runs of constant setpoint. The machine ramps between setpoints over a few seconds,\n * producing 1-2 s blocks; those merge into the following interval so it includes its own\n * acceleration. Fewer than two intervals is not an interval workout: returns `[]`.\n */\nfunction workloadBounds(samples: readonly AnalyticsSample[], channels: readonly string[]): Bound[] {\n const keys = SETPOINT_CHANNELS.filter((c) => channels.includes(c));\n if (keys.length === 0) return [];\n const sig = (row: AnalyticsSample) => keys.map((k) => round(row[k] ?? 0, 1));\n const same = (a: number[], b: number[]) => a.every((v, i) => v === b[i]);\n\n const blocks: { sig: number[]; start: number; end: number }[] = [];\n for (const row of samples) {\n const s = sig(row);\n const last = blocks[blocks.length - 1];\n if (last && same(last.sig, s)) last.end = row.t;\n else blocks.push({ sig: s, start: row.t, end: row.t });\n }\n\n const merged: typeof blocks = [];\n let carryStart: number | null = null;\n for (const b of blocks) {\n if (b.end - b.start + 1 < MIN_INTERVAL_SEC) {\n carryStart ??= b.start;\n continue;\n }\n merged.push({ sig: b.sig, start: carryStart ?? b.start, end: b.end });\n carryStart = null;\n }\n if (carryStart !== null) {\n const lastMerged = merged[merged.length - 1];\n const lastBlock = blocks[blocks.length - 1]!;\n if (lastMerged) lastMerged.end = lastBlock.end;\n else merged.push({ sig: blocks[0]!.sig, start: blocks[0]!.start, end: lastBlock.end });\n }\n if (merged.length < 2) return [];\n return merged.map((m, i) => [\n m.start - 1,\n m.end,\n { interval: i + 1, setpoint: Object.fromEntries(keys.map((k, j) => [k, m.sig[j]!])) },\n ]);\n}\n\n/**\n * Splits an exercise into segments and aggregates every channel per segment. Cumulative channels\n * report the distance (or calories, MOVEs) covered within the segment. A segment covers samples\n * with `startSec < t <= endSec`.\n */\nexport function segmentAnalytics(\n samples: readonly AnalyticsSample[],\n channels: readonly string[],\n laps: readonly Lap[],\n options: SegmentOptions = {},\n): { mode: SegmentMode; segments: Segment[] } {\n const segmentBy = options.segmentBy ?? \"auto\";\n if (samples.length === 0) return { mode: segmentBy === \"auto\" ? \"fixed\" : segmentBy, segments: [] };\n\n let mode: SegmentMode = \"fixed\";\n let bounds: Bound[] = [];\n if (segmentBy === \"auto\" || segmentBy === \"workload\") {\n bounds = workloadBounds(samples, channels);\n if (bounds.length) mode = \"workload\";\n }\n if (bounds.length === 0 && (segmentBy === \"auto\" || segmentBy === \"laps\") && laps.length) {\n let prev = 0;\n for (const lap of laps) {\n if (lap.totalTimeSec == null) continue;\n bounds.push([prev, lap.totalTimeSec, { lap: lap.n, lapDistanceM: lap.lapDistanceM }]);\n prev = lap.totalTimeSec;\n }\n if (bounds.length) mode = \"laps\";\n }\n if (bounds.length === 0) {\n mode = \"fixed\";\n const step = Math.max(10, Math.trunc(options.segmentSeconds ?? 300) || 300);\n const lastT = Math.max(...samples.map((r) => r.t));\n for (let s = 0; s < lastT; s += step) bounds.push([s, Math.min(s + step, lastT), {}]);\n }\n\n const segments: Segment[] = [];\n for (const [start, end, meta] of bounds) {\n const rows = samples.filter((r) => start < r.t && r.t <= end);\n if (rows.length === 0) continue;\n const stats: Record<string, ChannelStats | CumulativeDelta> = channelStats(rows, channels);\n for (const ch of Object.keys(stats)) {\n if (!CUMULATIVE_CHANNELS.has(ch)) continue;\n const vals = numbers(rows, ch);\n stats[ch] = { delta: round(vals[vals.length - 1]! - vals[0]!, 2), end: vals[vals.length - 1]! };\n }\n const segment: Segment = { startSec: start, endSec: end, durationSec: end - start, ...meta, channels: stats };\n const speed = (stats[\"Speed\"] as ChannelStats | undefined)?.avg;\n if (speed) segment.paceMinPerKm = round(60 / speed, 2);\n segments.push(segment);\n }\n return { mode, segments };\n}\n\n/** Rows with `fromSec <= t <= toSec`; `toSec` of 0 or less means no upper bound. */\nexport function windowSamples<T extends { t: number }>(rows: readonly T[], fromSec = 0, toSec = 0): T[] {\n const lo = Math.max(0, fromSec);\n return rows.filter((r) => r.t >= lo && (toSec <= 0 || r.t <= toSec));\n}\n\n/** Every `step`th row, starting with the first. */\nexport function downsample<T>(rows: readonly T[], step: number): T[] {\n const n = Math.max(1, Math.trunc(step) || 1);\n return rows.filter((_, i) => i % n === 0);\n}\n","import { channelStats, segmentAnalytics, type ChannelStats, type Segment, type SegmentMode, type SegmentOptions } from \"./analysis.js\";\nimport type { TechnogymClient } from \"./client.js\";\nimport { TechnogymError, TechnogymNotFoundError } from \"./errors.js\";\nimport {\n parseExerciseAnalytics,\n parseHrSession,\n parseSessionSummary,\n parseWorkoutSession,\n} from \"./parse.js\";\nimport type {\n ExerciseAnalytics,\n HrSession,\n RawPayload,\n SessionSummary,\n UserInfo,\n WorkoutSession,\n} from \"./types.js\";\nimport { addDays, toIsoDate, toPartitionKey } from \"./util/dates.js\";\n\nconst HISTORY_PATH = \"/Training/User/{userId}/ActivityHistory\";\nconst SESSION_PATH = \"/Training/User/{userId}/GetPerformedWorkoutSessionByIdCr\";\nconst HR_SESSION_PATH = \"/Training/User/{userId}/GetHrSession\";\nconst MOVERGY_PATH = \"/Training/User/{userId}/MyMovergy\";\nconst analyticsPath = (analyticsId: string) => `/Training/CardioLog/${encodeURIComponent(analyticsId)}/Details`;\n\n/** Options for `listSessions`. */\nexport interface ListSessionsOptions {\n /**\n * Only gym-floor workout sessions (`justThisType: WorkoutSession`). Default `true`; `false`\n * includes every activity type (classes, outdoor, ...).\n */\n onlyWorkouts?: boolean;\n}\n\n/** `getExerciseAnalytics` plus the derived stats and segments of `analyzeExercise`. */\nexport interface AnalyzedExercise extends ExerciseAnalytics {\n /** Per-channel avg / max / min over the full 1 Hz data. */\n channelStats: Record<string, ChannelStats>;\n segmentMode: SegmentMode;\n segments: Segment[];\n}\n\n/**\n * The Technogym mywellness API, one method per endpoint. Wraps a `TechnogymClient`:\n *\n * ```ts\n * const client = new TechnogymClient({ sessionStore: new FileSessionStore(\"./session\") });\n * await client.login(email, password);\n * const technogym = new Technogym(client);\n * const sessions = await technogym.listSessions(\"2026-09-01\", \"2026-09-30\");\n * ```\n *\n * A session is addressed by its `sessionId` AND its date: the API partitions sessions by day.\n * Dates accept a `YYYY-MM-DD` string or a `Date` (its local calendar day). Read-only: there is\n * no method that changes anything on the account.\n */\nexport class Technogym {\n /** Wraps a client. It signs in on first use when it was given credentials. */\n constructor(\n /** The client every request goes through. */\n readonly client: TechnogymClient,\n ) {}\n\n /** The signed-in account: user id, name, culture and facilities. No HTTP call once signed in. */\n getUser(): Promise<UserInfo> {\n return this.client.user();\n }\n\n /** The account's current Movergy score, or `null` when mywellness has none. */\n async getMovergy(): Promise<number | null> {\n const data = await this.client.services<{ movergy?: number }>(MOVERGY_PATH);\n return typeof data?.movergy === \"number\" ? data.movergy : null;\n }\n\n /** The untouched `ActivityHistory` payload for a date range (bounds in either order). */\n getActivityHistoryRaw(\n from: string | Date,\n to: string | Date,\n options: ListSessionsOptions = {},\n ): Promise<RawPayload | null> {\n let start = toIsoDate(from);\n let end = toIsoDate(to);\n if (start > end) [start, end] = [end, start];\n const body: Record<string, string> = { startDay: toPartitionKey(start), endDay: toPartitionKey(end) };\n if (options.onlyWorkouts ?? true) body[\"justThisType\"] = \"WorkoutSession\";\n return this.client.services<RawPayload>(HISTORY_PATH, body);\n }\n\n /** Sessions between two dates (inclusive, either order), newest first. An overview only. */\n async listSessions(\n from: string | Date,\n to: string | Date,\n options: ListSessionsOptions = {},\n ): Promise<SessionSummary[]> {\n const data = await this.getActivityHistoryRaw(from, to, options);\n const items = Array.isArray(data?.[\"items\"]) ? (data[\"items\"] as unknown[]) : [];\n return items\n .map(parseSessionSummary)\n .sort((a, b) => (b.date + b.time).localeCompare(a.date + a.time));\n }\n\n /**\n * The date of a session, found by scanning the last `lookbackDays` (default 365) of history\n * across every activity type. Throws `TechnogymNotFoundError` when it is not there.\n */\n async findSessionDate(sessionId: string | number, lookbackDays = 365): Promise<string> {\n const today = toIsoDate(new Date());\n const sessions = await this.listSessions(addDays(today, -lookbackDays), today, { onlyWorkouts: false });\n const match = sessions.find((s) => s.sessionId === String(sessionId));\n if (!match) throw new TechnogymNotFoundError(`Session ${sessionId} not found in the last ${lookbackDays} days`);\n return match.date;\n }\n\n /** The untouched `GetPerformedWorkoutSessionByIdCr` payload. Pass `date`; see `getWorkoutSession`. */\n async getWorkoutSessionRaw(sessionId: string | number, date?: string | Date): Promise<RawPayload> {\n const day = await this.#resolveDate(sessionId, date);\n const data = await this.client.services<RawPayload>(SESSION_PATH, {\n idCr: idCr(sessionId),\n partitionDate: toPartitionKey(day),\n });\n if (!data?.[\"id\"]) throw new TechnogymNotFoundError(`Session ${sessionId} on ${day} not found`);\n return data;\n }\n\n /**\n * Every exercise of a session with machine, sets (performed and prescribed), the machine's full\n * summary and workload ranges, plus efficacy, muscle scores and totals. Summaries are averages\n * and maxima only: for splits and time series, call `getExerciseAnalytics` with an exercise's\n * `analyticsId`. `date` is strongly recommended; without it the last year is scanned for it.\n */\n async getWorkoutSession(sessionId: string | number, date?: string | Date): Promise<WorkoutSession> {\n return parseWorkoutSession(await this.getWorkoutSessionRaw(sessionId, date), String(sessionId));\n }\n\n /**\n * Every workout session of the last `days` days (1-365, default 14), fully loaded, newest first.\n * One request for the list plus one per session.\n */\n async getRecentWorkouts(days = 14): Promise<WorkoutSession[]> {\n const n = Math.max(1, Math.min(Math.trunc(days) || 14, 365));\n const today = toIsoDate(new Date());\n const items = await this.listSessions(addDays(today, -n), today);\n const sessions: WorkoutSession[] = [];\n // Sequential on purpose: this is an unofficial API, and a burst of parallel calls is the\n // fastest way to get a client throttled.\n for (const item of items) sessions.push(await this.getWorkoutSession(item.sessionId, item.date));\n return sessions;\n }\n\n /** The untouched `CardioLog/{analyticsId}/Details` payload. */\n async getExerciseAnalyticsRaw(analyticsId: string): Promise<RawPayload> {\n if (!analyticsId) throw new TechnogymError(\"analyticsId is required\");\n const data = await this.client.services<RawPayload>(analyticsPath(analyticsId));\n if (!data || Object.keys(data).length === 0) throw new TechnogymNotFoundError(`No analytics for ${analyticsId}`);\n return data;\n }\n\n /**\n * Per-second analytics of one exercise: the machine's channels (Skillrun: Speed, Grade,\n * HDistance, RunningCadence, RunningPower; bikes: Power, Rpm...), heart rate, HR zones, laps and\n * summary. Strength machines have no time series; use the sets of `getWorkoutSession`.\n */\n async getExerciseAnalytics(analyticsId: string): Promise<ExerciseAnalytics> {\n return parseExerciseAnalytics(await this.getExerciseAnalyticsRaw(analyticsId), analyticsId);\n }\n\n /**\n * `getExerciseAnalytics` plus per-channel stats and segments: by default one per interval\n * detected from setpoint changes (speed / grade / level), else per console lap, else fixed\n * windows. See `segmentAnalytics` to run the same analysis on samples you already have.\n */\n async analyzeExercise(analyticsId: string, options: SegmentOptions = {}): Promise<AnalyzedExercise> {\n const analytics = await this.getExerciseAnalytics(analyticsId);\n const { mode, segments } = segmentAnalytics(analytics.samples, analytics.channels, analytics.laps, options);\n return {\n ...analytics,\n channelStats: channelStats(analytics.samples, analytics.channels),\n segmentMode: mode,\n segments,\n };\n }\n\n /** The untouched `GetHrSession` payload. */\n async getSessionHeartRateRaw(sessionId: string | number, date?: string | Date): Promise<RawPayload> {\n const day = await this.#resolveDate(sessionId, date);\n const data = await this.client.services<RawPayload>(HR_SESSION_PATH, {\n idCr: idCr(sessionId),\n // A NUMBER here, unlike the string partitionDate of GetPerformedWorkoutSessionByIdCr.\n partitionDate: Number(toPartitionKey(day)),\n });\n if (!data || Object.keys(data).length === 0) {\n throw new TechnogymNotFoundError(`No heart-rate data for session ${sessionId}`);\n }\n return data;\n }\n\n /** The whole-session heart-rate trace: timestamped samples, HR zones and avg/min/max HR. */\n async getSessionHeartRate(sessionId: string | number, date?: string | Date): Promise<HrSession> {\n const day = await this.#resolveDate(sessionId, date);\n return parseHrSession(await this.getSessionHeartRateRaw(sessionId, day), String(sessionId), day);\n }\n\n async #resolveDate(sessionId: string | number, date: string | Date | undefined): Promise<string> {\n return date === undefined ? this.findSessionDate(sessionId) : toIsoDate(date);\n }\n}\n\nfunction idCr(sessionId: string | number): number {\n const n = Number(sessionId);\n if (!Number.isSafeInteger(n)) throw new TechnogymError(`sessionId must be an integer, got \"${sessionId}\"`);\n return n;\n}\n"],"mappings":"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","names":["text","text"]}
|