@fytkit/sdk 4.0.0 → 4.2.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/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to `@fytkit/sdk` are documented in this file.
4
4
  Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/).
5
5
  Versioning: [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 4.2.0
8
+
9
+ To be published by hand with sdk-publish.yml (the manifest records 4.2.0). Create the tag and GitHub release `@fytkit/sdk@4.2.0` when it is published.
10
+
11
+ ### Features
12
+
13
+ * `nutritionFollows`: the meal plan the trainee follows, one at a time. `list()` resolves to the active follow with its plan (`{ id, planId, status, startDate, startedAt, stoppedAt, plan: { id, title, description, accessLevel, canAccess, status, durationWeeks, headerImageUrl } }`), or to `[]`. `start({ planId, startDate? })` follows one of the studio's published plans from `startDate` ("YYYY-MM-DD", day 1 of week 1 in the trainee's calendar; their local today when omitted) and stops the trainee's current follow of another plan in the same write, naming it in `stoppedFollowId`; following the plan already followed changes nothing and returns that follow. `stop({ id })` stops a follow. A premium plan without `premium_nutrition` rejects as `nutrition.get` does (`payment_required` or `forbidden`); a draft, another studio's or an unknown plan, and another trainee's follow, reject with `not_found`; stopping a follow already stopped rejects with `conflict`. Writes are refused while a fytkit admin is signed in as the trainee. Additive ([#594](https://github.com/cbarlow1993/fytkit-web/issues/594))
14
+ * `auth.signInWithIdToken({ provider, idToken, nonce, name? })`: Sign in with Apple or Google in Fytkit Coach. Signs in, signs up, or joins this studio with the provider's ID token, and resolves like `signIn`: `{ kind: "signed_in", session, memberCreated }` (the session is cached and `signed_in` emitted; `memberCreated` is true when this call made the person a member of this studio, for onboarding) or `{ kind: "two_factor_required", challenge }`, completed with the existing `verify*` methods. `nonce` is the raw nonce the app generated (pass its SHA-256 hex to Apple, the raw nonce to Google); `name` is the name Apple gives the app on the first authorisation only. A credential call: a 401 never refreshes or signs out. Studio-owned (custom) apps are refused. New types `IdTokenProvider` and `IdTokenSignInResult` ([#544](https://github.com/cbarlow1993/fytkit-web/issues/544))
15
+ * `FytkitErrorCode` gains `social_sign_in_unavailable` and `social_email_has_password`, `social_email_has_other_provider`, `two_factor_join_unsupported` (on `forbidden`, 403) and `invalid_id_token`, `social_email_unverified` (on `server`, 400), the refusals of `signInWithIdToken`; `first_party_account` is sent by it too ([#544](https://github.com/cbarlow1993/fytkit-web/issues/544))
16
+ * `auth.closeAccount` accepts a fresh Apple or Google ID token instead of a password, so a member whose account has no password (it signs in with Apple or Google only) can close it in the app: `closeAccount({ confirmEmail, identityToken: { provider, idToken, nonce, authorizationCode? } })`. Run the provider's sign-in again with a new nonce; the token must be at most 10 minutes old and belong to the Apple or Google account the member signs in with. An account with a password still confirms with `closeAccount({ confirmEmail, password })`. The input is the new exported union `CloseAccountInput`, so sending both proofs, or neither, does not compile; the existing password call is unchanged. New type `IdTokenReauthentication`. On success the session is cleared as before ([#544](https://github.com/cbarlow1993/fytkit-web/issues/544))
17
+ * `auth.signInWithIdToken` takes an optional `authorizationCode` (Apple only; a validation error with Google): Apple's authorization code from the same sign-in, which the server exchanges for a refresh token so the app's Apple tokens can be revoked when the account is closed. `closeAccount`'s `identityToken.authorizationCode` does the same for a member whose code was never sent. A failed exchange never fails sign-in. Additive ([#544](https://github.com/cbarlow1993/fytkit-web/issues/544))
18
+ * `FytkitErrorCode` gains `password_not_set` (on `server`, 400: `closeAccount` was sent a password but the account has none; send `identityToken`), `password_required` (on `server`, 400: `closeAccount` was sent `identityToken` but the account has a password) and `reauth_identity_mismatch` (on `forbidden`, 403: the ID token is for another Apple or Google account than the member's). `invalid_id_token` and `social_sign_in_unavailable` can now come from `closeAccount` too. Every `closeAccount` refusal keeps the session ([#544](https://github.com/cbarlow1993/fytkit-web/issues/544))
19
+
20
+ ### Changed (server)
21
+
22
+ * `nutrition.list` and `recipes.list` now return premium plans and recipes the caller cannot open, with `canAccess: false`, where before they left them out. The gate is unchanged on `nutrition.get` and `recipes.get` (`payment_required`). This is a server change, so every SDK version sees it ([#597](https://github.com/cbarlow1993/fytkit-web/issues/597))
23
+
24
+ ## 4.1.0
25
+
26
+ To be published by hand with sdk-publish.yml (the manifest records 4.1.0). Create the tag and GitHub release `@fytkit/sdk@4.1.0` when it is published.
27
+
28
+ ### Features
29
+
30
+ * `subscribeToMessages` takes an optional `onCoachTyping`, for a "Sam is writing…" line: who on the trainee's coaching team is writing to them right now, as `{ kind: "typing", names }` (staff display names, as the conversation shows them) or `{ kind: "idle" }`. Heard only while the status is `live`; a name drops out by itself `COACH_TYPING_EXPIRY_MS` (8 seconds) after its last sign, so a lost stop never leaves it showing, and the state goes `idle` when the channel is lost. Additive. New type `CoachTypingState` and constant `COACH_TYPING_EXPIRY_MS` (W16)
31
+ * `programs.get` and `programs.getSession` carry `durationMinutes` on each session's workout: the trainer's planned time for the workout, null when unset. Additive (W13)
32
+ * `auth.getSecurityStatus()` adds `passwordChangedAt` (when the password was last set or changed, ISO 8601; null when the account has no password) and `backupCodesRemaining` (unused backup codes left; null when two-factor is off, 0 when every code is used), and `SecurityStatus` gains both fields. Additive (W20)
33
+ * `profile.get` and `profile.update` carry `lengthUnit` (`"in"` or `"cm"`), the member's unit for body measurements, beside `weightUnit`; `profile.update` takes it optionally. Additive (W19)
34
+ * `bodyMeasurements.record` takes an optional `clientRequestId` (a UUID) for offline replay, as `bodyMetrics.record` does: a replay with the same key returns the stored measurements instead of recording them twice. Calls without a key are unchanged (W21)
35
+
36
+ ### Bug Fixes
37
+
38
+ * `auth.disableTwoFactor` now signs out every other device, and the server rotates the caller's session; the SDK swaps its stored token transparently, so the caller stays signed in ([#494](https://github.com/cbarlow1993/fytkit-web/pull/494))
39
+
7
40
  ## 4.0.0
8
41
 
9
42
  To be published by hand (release-please, which runs on GitHub Actions, does not cut this release; the manifest records 4.0.0 so the next automated release bumps from it). Create the tag and GitHub release `@fytkit/sdk@4.0.0` when it is published, or release-please proposes 4.0.0 again.
package/README.md CHANGED
@@ -161,6 +161,13 @@ await fytkit.auth.changePassword({ currentPassword, newPassword, revokeOtherSess
161
161
  // nothing changes until the trainee opens it.
162
162
  await fytkit.auth.changeEmail({ newEmail });
163
163
 
164
+ // Resend the sign-up verification email to the trainee's own address (there
165
+ // is no email input). Resolves to { status: "sent" } or, when the address is
166
+ // already verified and nothing is sent, { status: "already_verified" }.
167
+ // Throttled per account (one a minute, five an hour): over the limit it
168
+ // rejects with { kind: "rate_limited", retryAfter } (milliseconds).
169
+ const { status } = await fytkit.auth.resendVerification();
170
+
164
171
  // Devices signed in through this app (plus the calling one); the trainee's
165
172
  // sessions elsewhere are never listed. revokeId is an opaque id (never the
166
173
  // bearer token).
@@ -181,7 +188,7 @@ session is gone. A wrong password on `signIn` (for example, signing in again
181
188
  after `reauth_required`) is a plain `auth_required` and leaves the current
182
189
  session alone. An account that also runs or works at a trainer on Fytkit can't
183
190
  sign in, sign up or complete two-factor through an app, and an existing app
184
- session can't change its password or email or close it:
191
+ session can't change its password or email, resend its verification email or close it:
185
192
  `{ kind: "forbidden", code: "first_party_account" }`; point it to Fytkit's own
186
193
  sign-in. Enrolling twice in a program is `{ kind: "conflict" }` with the
187
194
  existing `enrollmentId`.
@@ -251,6 +258,23 @@ const unsubscribe = subscribeToMessages(fytkit, {
251
258
  unsubscribe();
252
259
  ```
253
260
 
261
+ To show "Sam is writing…" while someone on the trainee's coaching team
262
+ writes a reply, pass `onCoachTyping`. It reports `{ kind: "typing", names }`
263
+ (display names, as the conversation shows them on their messages) and
264
+ `{ kind: "idle" }`. It is live only: a name drops out by itself 8 seconds
265
+ after its last sign (`COACH_TYPING_EXPIRY_MS`), so a lost "stopped" never
266
+ leaves it showing, it goes `idle` when the channel is lost, and while you
267
+ poll it never shows.
268
+
269
+ ```ts
270
+ subscribeToMessages(fytkit, {
271
+ realtime,
272
+ onChange: () => refreshConversation(),
273
+ onCoachTyping: (typing) =>
274
+ setWritingLine(typing.kind === "typing" ? `${firstName(typing.names[0])} is writing…` : null),
275
+ });
276
+ ```
277
+
254
278
  Polling spends your app's per-minute request limit, which all its users
255
279
  share, so stop as soon as the status is `live`. The full guide:
256
280
  [`messages.realtimeToken`](../../docs/developer/reference/data-namespaces.md#messagesrealtimetoken)
@@ -269,7 +293,8 @@ await fytkit.preferences.updateNotifications({ streakAlerts: false }); // partia
269
293
 
270
294
  - `resolveJoinCode` (a studio's join code to its client id, name, logo and app theme, with no client) and `app.bootstrap` (the launch read, revalidated with its ETag), with `resolvePaymentMode` ("Subscribe" link-out or "What your plan includes", per storefront)
271
295
  - `auth.signUp` / `signIn` / `signOut` / `getSession` / `onAuthStateChange`
272
- - `auth.requestPasswordReset` / `confirmPasswordReset` / `confirmEmail`
296
+ - `auth.signInWithIdToken` (Sign in with Apple or Google, Fytkit Coach only: pass the raw nonce; Apple gets its SHA-256 hex, Google the raw nonce)
297
+ - `auth.requestPasswordReset` / `confirmPasswordReset` / `confirmEmail` / `resendVerification`
273
298
  - Two-factor: `auth.verifyTotp` / `verifyBackupCode` / `requestEmailOtp` / `verifyEmailOtp` (sign-in), `enableTotp` / `verifyTotpSetup` / `disableTwoFactor` / `regenerateBackupCodes` / `getSecurityStatus` (management)
274
299
  - Account security: `auth.changePassword` / `changeEmail` / `confirmEmailChange` / `listSessions` / `revokeSession` / `revokeOtherSessions` / `closeAccount`
275
300
  - Data namespaces: `workouts`, `programs`, `recipes`, `nutrition`, `nutritionLog`, `profile`, `preferences`, `articles`, `enrollments`, `sessionLogs`, `progression`, `progress`, `bodyMetrics`, `bodyMeasurements`, `progressPhotos`, `checkIns`, `messages`, `timeline`, `health`, `habits`, `media`, `subscription`
package/dist/index.d.ts CHANGED
@@ -156,6 +156,49 @@ declare type ClientEntitlementCode = "premium_workouts" | "premium_programs" | "
156
156
 
157
157
  declare type ClientEntitlements = readonly ClientEntitlementCode[] | "all";
158
158
 
159
+ /**
160
+ * The input to `auth.closeAccount()`: the member's email as confirmation
161
+ * plus exactly one proof. `password` for an account with a password;
162
+ * `identityToken` for an account that signs in with Apple or Google only
163
+ * and has no password. Sending both, or neither, does not compile.
164
+ */
165
+ export declare type CloseAccountInput = {
166
+ confirmEmail: string;
167
+ password: string;
168
+ identityToken?: never;
169
+ } | {
170
+ confirmEmail: string;
171
+ identityToken: IdTokenReauthentication;
172
+ password?: never;
173
+ };
174
+
175
+ /**
176
+ * How long a staff member stays in {@link CoachTypingState}'s `names` after
177
+ * the last sign that they are writing. The dashboard repeats the sign every
178
+ * 3 seconds while they write.
179
+ */
180
+ export declare const COACH_TYPING_EXPIRY_MS = 8000;
181
+
182
+ /**
183
+ * Whether the trainee's coaching team is writing to them right now, for a
184
+ * "Sam is writing…" line under the conversation:
185
+ * - `idle`: nobody is.
186
+ * - `typing`: the staff members in `names` are, by the display name the
187
+ * conversation shows on their messages, each name once, in the order
188
+ * they started. Show the first, or "Sam and Alex are writing…".
189
+ *
190
+ * A hint, live only: it is heard only while the status is `live`, and goes
191
+ * back to `idle` when the channel is lost. A name that hears nothing more
192
+ * for 8 seconds ({@link COACH_TYPING_EXPIRY_MS}) drops out by itself, so a
193
+ * lost "stopped" never leaves it showing. While polling it stays `idle`.
194
+ */
195
+ export declare type CoachTypingState = {
196
+ readonly kind: "idle";
197
+ } | {
198
+ readonly kind: "typing";
199
+ readonly names: readonly string[];
200
+ };
201
+
159
202
  export declare function createFytkitClient(options: FytkitClientOptions): FytkitClient;
160
203
 
161
204
  /**
@@ -169,7 +212,7 @@ export declare function createMemoryStorageAdapter(): TokenStorage;
169
212
 
170
213
  declare interface DeveloperAppRequestContext {
171
214
  readonly headers: Headers;
172
- readonly surface?: RequestSurface;
215
+ readonly surface: RequestSurface;
173
216
  }
174
217
 
175
218
  /** The full SDK client surface. `auth.*` plus the public-router slices, minus auth (those live on `client.auth`). */
@@ -191,6 +234,23 @@ export declare interface FytkitClient {
191
234
  email: string;
192
235
  password: string;
193
236
  }) => Promise<SignInResult>;
237
+ /** Signs in, signs up or joins this studio with an Apple or Google ID
238
+ * token (Fytkit Coach only). `nonce` is the raw nonce the app generated:
239
+ * pass its SHA-256 hex to Apple, or the raw nonce to Google. `name` is the
240
+ * name Apple hands the app on the first authorisation (it is never in the
241
+ * token). `authorizationCode` is Apple's authorization code from the
242
+ * same sign-in (Apple only; a validation error with Google): the server
243
+ * exchanges it for a refresh token so it can revoke the app's Apple
244
+ * tokens when the account is closed. On `signed_in` the session is cached
245
+ * (emits signed_in); on `two_factor_required` continue with the `verify*`
246
+ * methods. Refusals carry a `code` (see `FytkitErrorCode`). */
247
+ signInWithIdToken: (input: {
248
+ provider: IdTokenProvider;
249
+ idToken: string;
250
+ nonce: string;
251
+ name?: string;
252
+ authorizationCode?: string;
253
+ }) => Promise<IdTokenSignInResult>;
194
254
  signOut: () => Promise<void>;
195
255
  /** Synchronously read the cached session, if any. */
196
256
  getSession: () => Session | null;
@@ -236,6 +296,8 @@ export declare interface FytkitClient {
236
296
  verifyTotpSetup: (input: {
237
297
  code: string;
238
298
  }) => Promise<void>;
299
+ /** Turns off 2FA and signs out every other device. The server rotates
300
+ * this session; the SDK swaps its stored token transparently. */
239
301
  disableTwoFactor: (input: {
240
302
  password: string;
241
303
  }) => Promise<void>;
@@ -283,14 +345,18 @@ export declare interface FytkitClient {
283
345
  * untouched; `changePassword` with `revokeOtherSessions: true` ends those. */
284
346
  revokeOtherSessions: () => Promise<void>;
285
347
  /** Permanently closes the member's account (cancels their subscriptions,
286
- * anonymises their data, deletes the login) after re-verifying the email
287
- * and password. On success the SDK clears its session and emits
288
- * `signed_out`. A wrong password rejects with `{ kind: "server", status:
289
- * 400, code: "invalid_password" }` and leaves the session signed in. */
290
- closeAccount: (input: {
291
- confirmEmail: string;
292
- password: string;
293
- }) => Promise<void>;
348
+ * anonymises their data, deletes the login, revokes the app's Sign in
349
+ * with Apple tokens) after re-verifying the email and one proof: the
350
+ * `password`, or, for an account without a password, a fresh Apple or
351
+ * Google `identityToken` (see `CloseAccountInput`). On success the SDK
352
+ * clears its session and emits `signed_out`. Every refusal leaves the
353
+ * session signed in: a wrong password rejects with `{ kind: "server",
354
+ * status: 400, code: "invalid_password" }`; the wrong proof for the
355
+ * account with `password_not_set` or `password_required` (`server`, 400);
356
+ * an ID token for another Apple or Google account with
357
+ * `reauth_identity_mismatch` (`forbidden`, 403); an unverifiable, reused
358
+ * or stale ID token with `invalid_id_token` (`server`, 400). */
359
+ closeAccount: (input: CloseAccountInput) => Promise<void>;
294
360
  };
295
361
  readonly workouts: PublicApi["workout"];
296
362
  readonly exercises: PublicApi["exercise"];
@@ -299,6 +365,15 @@ export declare interface FytkitClient {
299
365
  readonly equipment: PublicApi["equipment"];
300
366
  readonly recipes: PublicApi["recipe"];
301
367
  readonly nutrition: PublicApi["nutrition"];
368
+ /**
369
+ * The meal plan the trainee follows, one at a time: `list` (the active
370
+ * follow with its plan, or nothing), `start({ planId, startDate? })`, which
371
+ * stops the trainee's current follow of another plan, and `stop({ id })`.
372
+ * `startDate` is day 1 of week 1 in the trainee's own calendar, for
373
+ * "week 2 of 4, day 3". Writes are refused while a fytkit admin is signed
374
+ * in as the trainee.
375
+ */
376
+ readonly nutritionFollows: PublicApi["nutritionFollow"];
302
377
  readonly articles: PublicApi["article"];
303
378
  readonly enrollments: PublicApi["enrollment"];
304
379
  readonly favourites: PublicApi["favourite"];
@@ -440,13 +515,53 @@ export declare type FytkitError = {
440
515
  * - `"first_party_account"` (`forbidden`, 403) — the account also runs or
441
516
  * works at a trainer on Fytkit, so an app may not sign it in, sign it up,
442
517
  * reset its password or handle its two-factor steps (`signIn`, `signUp`,
443
- * the `verify*` and TOTP methods), and an existing session can't change
518
+ * `signInWithIdToken`, the `verify*` and TOTP methods), and an existing session can't change
444
519
  * its password or email or close it (`changePassword`, `changeEmail`,
445
520
  * `closeAccount`). The credentials were right; tell the
446
521
  * person to sign in with Fytkit itself. `requestPasswordReset` never sends
447
- * this: it answers `ok` and sends no email.
522
+ * this: it answers `ok` and sends no email. `signInWithIdToken` also sends
523
+ * it for a Fytkit platform administrator.
524
+ *
525
+ * `closeAccount` re-authentication refusals (see `CloseAccountInput`):
526
+ *
527
+ * - `"password_not_set"` (`server`, 400) — `password` was sent, but the
528
+ * account has no password (it signs in with Apple or Google). Re-run the
529
+ * provider's sign-in and send `identityToken` instead.
530
+ * - `"password_required"` (`server`, 400) — `identityToken` was sent, but the
531
+ * account has a password. Ask for the password; an account with a password
532
+ * always confirms with it.
533
+ * - `"reauth_identity_mismatch"` (`forbidden`, 403) — the ID token is valid
534
+ * but is not for the Apple or Google account the member signs in with.
535
+ * Ask them to use that account. The session is untouched.
536
+ *
537
+ * `closeAccount` with `identityToken` can also send `invalid_id_token` (the
538
+ * token could not be verified, was already used, or is older than 10
539
+ * minutes) and `social_sign_in_unavailable`, with the meanings below.
540
+ *
541
+ * `signInWithIdToken` refusals (the ID token is checked first, except for
542
+ * `social_sign_in_unavailable`); the first two are also `closeAccount`
543
+ * refusals:
544
+ *
545
+ * - `"social_sign_in_unavailable"` (`forbidden`, 403) — this app can't use
546
+ * Sign in with Apple or Google: the provider is not configured for it, or
547
+ * the app is a studio's own (custom) app. Hide the button.
548
+ * - `"invalid_id_token"` (`server`, 400) — the ID token could not be
549
+ * verified (expired, wrong audience, nonce mismatch, already used, or no
550
+ * email on a first sign-in). Start the provider's sign-in again with a new
551
+ * nonce.
552
+ * - `"social_email_unverified"` (`server`, 400) — the provider has not
553
+ * verified the account's email address.
554
+ * - `"social_email_has_password"` (`forbidden`, 403) — the email already
555
+ * belongs to an account with a password. Ask the person to sign in with
556
+ * their password; accounts are never linked implicitly.
557
+ * - `"social_email_has_other_provider"` (`forbidden`, 403) — the email
558
+ * already belongs to an account that signs in with the other provider.
559
+ * Ask the person to use that one.
560
+ * - `"two_factor_join_unsupported"` (`forbidden`, 403) — the account uses
561
+ * two-step verification and is not yet a member of this studio; it can't
562
+ * join a new studio from the app.
448
563
  */
449
- export declare type FytkitErrorCode = "reauth_required" | "invalid_password" | "invalid_code" | "first_party_account" | (string & {});
564
+ export declare type FytkitErrorCode = "reauth_required" | "invalid_password" | "invalid_code" | "first_party_account" | "social_sign_in_unavailable" | "invalid_id_token" | "social_email_unverified" | "social_email_has_password" | "social_email_has_other_provider" | "two_factor_join_unsupported" | "password_not_set" | "password_required" | "reauth_identity_mismatch" | (string & {});
450
565
 
451
566
  /**
452
567
  * The server's structured refusal on a `FytkitError`: why it said no and
@@ -663,6 +778,43 @@ export declare interface FytkitRefusalRetryAt {
663
778
  readonly retryAfterMs?: number;
664
779
  }
665
780
 
781
+ /** The identity providers whose ID tokens `auth.signInWithIdToken` accepts. */
782
+ export declare type IdTokenProvider = "apple" | "google";
783
+
784
+ /**
785
+ * A fresh Apple or Google ID token that proves the signed-in member is
786
+ * present, for `auth.closeAccount` on an account without a password. Run
787
+ * the provider's sign-in again with a new nonce, as for `signInWithIdToken`:
788
+ * `nonce` is the raw nonce the app generated (pass its SHA-256 hex to Apple,
789
+ * the raw nonce to Google). The token must be at most 10 minutes old and
790
+ * belong to the Apple or Google account the member signs in with.
791
+ * `authorizationCode` is Apple's authorization code from that same sign-in
792
+ * (Apple only); send it so the server can revoke the app's Apple tokens.
793
+ */
794
+ export declare interface IdTokenReauthentication {
795
+ provider: IdTokenProvider;
796
+ idToken: string;
797
+ nonce: string;
798
+ authorizationCode?: string;
799
+ }
800
+
801
+ /**
802
+ * The discriminated return value from `auth.signInWithIdToken()`. As
803
+ * `SignInResult`, plus `memberCreated` on `"signed_in"`: true when this call
804
+ * made the person a member of this studio (a new account, or an existing
805
+ * account joining this studio for the first time), so the app can show its
806
+ * onboarding. `"two_factor_required"` continues with the same `verify*`
807
+ * methods as `signIn`'s challenge.
808
+ */
809
+ export declare type IdTokenSignInResult = {
810
+ readonly kind: "signed_in";
811
+ readonly session: Session;
812
+ readonly memberCreated: boolean;
813
+ } | {
814
+ readonly kind: "two_factor_required";
815
+ readonly challenge: TwoFactorChallenge;
816
+ };
817
+
666
818
  /** A coach a join code's studio names publicly. */
667
819
  export declare interface JoinCodeCoach {
668
820
  /** The coach's public name. */
@@ -906,6 +1058,46 @@ declare const publicRouter: {
906
1058
  }>>;
907
1059
  }, $strip>], "kind">;
908
1060
  }, $strip>, Record<never, never>, Record<never, never>>;
1061
+ signInWithIdToken: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, {
1062
+ trainerId: string;
1063
+ appId: ActiveDeveloperAppId;
1064
+ clientId: string | undefined;
1065
+ hasActiveSubscription: boolean;
1066
+ clientEntitlements: ClientEntitlements;
1067
+ authMode: "dev-app";
1068
+ developerApp: CallingDeveloperApp;
1069
+ impersonatedBy: string | null;
1070
+ }>, ZodObject< {
1071
+ provider: ZodEnum< {
1072
+ apple: "apple";
1073
+ google: "google";
1074
+ }>;
1075
+ idToken: ZodString;
1076
+ nonce: ZodString;
1077
+ name: ZodOptional<ZodString>;
1078
+ authorizationCode: ZodOptional<ZodString>;
1079
+ }, $strip>, ZodObject< {
1080
+ data: ZodDiscriminatedUnion<[ZodObject< {
1081
+ kind: ZodLiteral<"signed_in">;
1082
+ user: ZodObject< {
1083
+ id: ZodString;
1084
+ email: ZodString;
1085
+ }, $strip>;
1086
+ session: ZodObject< {
1087
+ accessToken: ZodString;
1088
+ expiresAt: ZodString;
1089
+ }, $strip>;
1090
+ memberCreated: ZodBoolean;
1091
+ }, $strip>, ZodObject< {
1092
+ kind: ZodLiteral<"two_factor_required">;
1093
+ twoFactorToken: ZodString;
1094
+ availableMethods: ZodArray<ZodEnum< {
1095
+ totp: "totp";
1096
+ backup_code: "backup_code";
1097
+ email_otp: "email_otp";
1098
+ }>>;
1099
+ }, $strip>], "kind">;
1100
+ }, $strip>, Record<never, never>, Record<never, never>>;
909
1101
  signOut: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, {
910
1102
  trainerId: string;
911
1103
  appId: ActiveDeveloperAppId;
@@ -1059,6 +1251,7 @@ declare const publicRouter: {
1059
1251
  }, $strip>, ZodObject< {
1060
1252
  data: ZodObject< {
1061
1253
  success: ZodLiteral<true>;
1254
+ accessToken: ZodString;
1062
1255
  }, $strip>;
1063
1256
  }, $strip>, Record<never, never>, Record<never, never>>;
1064
1257
  regenerateBackupCodes: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, {
@@ -1179,6 +1372,8 @@ declare const publicRouter: {
1179
1372
  twoFactorEnabled: ZodBoolean;
1180
1373
  totpEnabled: ZodBoolean;
1181
1374
  hasBackupCodes: ZodBoolean;
1375
+ passwordChangedAt: ZodNullable<ZodString>;
1376
+ backupCodesRemaining: ZodNullable<ZodNumber>;
1182
1377
  }, $strip>;
1183
1378
  }, $strip>, Record<never, never>, Record<never, never>>;
1184
1379
  changePassword: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, {
@@ -1243,7 +1438,16 @@ declare const publicRouter: {
1243
1438
  impersonatedBy: string | null;
1244
1439
  }>, ZodObject< {
1245
1440
  confirmEmail: ZodString;
1246
- password: ZodString;
1441
+ password: ZodOptional<ZodString>;
1442
+ identityToken: ZodOptional<ZodObject< {
1443
+ provider: ZodEnum< {
1444
+ apple: "apple";
1445
+ google: "google";
1446
+ }>;
1447
+ idToken: ZodString;
1448
+ nonce: ZodString;
1449
+ authorizationCode: ZodOptional<ZodString>;
1450
+ }, $strip>>;
1247
1451
  }, $strip>, ZodObject< {
1248
1452
  data: ZodObject< {
1249
1453
  success: ZodLiteral<true>;
@@ -1694,6 +1898,63 @@ declare const publicRouter: {
1694
1898
  }, $strip>;
1695
1899
  }, $strip>, Record<never, never>, Record<never, never>>;
1696
1900
  };
1901
+ nutritionFollow: {
1902
+ list: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, ApiKeyContext>, Schema<unknown, unknown>, ZodObject< {
1903
+ data: ZodArray<ZodObject< {
1904
+ id: ZodString;
1905
+ planId: ZodString;
1906
+ status: ZodEnum< {
1907
+ active: "active";
1908
+ stopped: "stopped";
1909
+ }>;
1910
+ startDate: ZodISODate;
1911
+ startedAt: ZodString;
1912
+ stoppedAt: ZodNullable<ZodString>;
1913
+ plan: ZodObject< {
1914
+ id: ZodString;
1915
+ title: ZodString;
1916
+ description: ZodString;
1917
+ accessLevel: ZodEnum< {
1918
+ free: "free";
1919
+ premium: "premium";
1920
+ }>;
1921
+ canAccess: ZodBoolean;
1922
+ status: ZodEnum< {
1923
+ draft: "draft";
1924
+ published: "published";
1925
+ archived: "archived";
1926
+ }>;
1927
+ durationWeeks: ZodNumber;
1928
+ headerImageUrl: ZodNullable<ZodString>;
1929
+ }, $strip>;
1930
+ }, $strip>>;
1931
+ }, $strip>, Record<never, never>, Record<never, never>>;
1932
+ start: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, ApiKeyContext>, ZodObject< {
1933
+ planId: ZodString;
1934
+ startDate: ZodOptional<ZodISODate>;
1935
+ }, $strip>, ZodObject< {
1936
+ data: ZodObject< {
1937
+ id: ZodString;
1938
+ planId: ZodString;
1939
+ status: ZodEnum< {
1940
+ active: "active";
1941
+ stopped: "stopped";
1942
+ }>;
1943
+ startDate: ZodISODate;
1944
+ startedAt: ZodString;
1945
+ stoppedAt: ZodNullable<ZodString>;
1946
+ stoppedFollowId: ZodNullable<ZodString>;
1947
+ }, $strip>;
1948
+ }, $strip>, Record<never, never>, Record<never, never>>;
1949
+ stop: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, ApiKeyContext>, ZodObject< {
1950
+ id: ZodString;
1951
+ }, $strip>, ZodObject< {
1952
+ data: ZodObject< {
1953
+ id: ZodString;
1954
+ status: ZodLiteral<"stopped">;
1955
+ }, $strip>;
1956
+ }, $strip>, Record<never, never>, Record<never, never>>;
1957
+ };
1697
1958
  nutritionLog: {
1698
1959
  getDay: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, ApiKeyContext>, ZodObject< {
1699
1960
  date: ZodString;
@@ -1830,6 +2091,10 @@ declare const publicRouter: {
1830
2091
  lb: "lb";
1831
2092
  kg: "kg";
1832
2093
  }>;
2094
+ lengthUnit: ZodEnum< {
2095
+ in: "in";
2096
+ cm: "cm";
2097
+ }>;
1833
2098
  timezone: ZodNullable<ZodString>;
1834
2099
  fitnessGoal: ZodNullable<ZodEnum< {
1835
2100
  lose_fat: "lose_fat";
@@ -1857,6 +2122,10 @@ declare const publicRouter: {
1857
2122
  lb: "lb";
1858
2123
  kg: "kg";
1859
2124
  }>>;
2125
+ lengthUnit: ZodOptional<ZodEnum< {
2126
+ in: "in";
2127
+ cm: "cm";
2128
+ }>>;
1860
2129
  fitnessGoal: ZodOptional<ZodNullable<ZodEnum< {
1861
2130
  lose_fat: "lose_fat";
1862
2131
  build_muscle: "build_muscle";
@@ -1876,6 +2145,10 @@ declare const publicRouter: {
1876
2145
  lb: "lb";
1877
2146
  kg: "kg";
1878
2147
  }>;
2148
+ lengthUnit: ZodEnum< {
2149
+ in: "in";
2150
+ cm: "cm";
2151
+ }>;
1879
2152
  timezone: ZodNullable<ZodString>;
1880
2153
  fitnessGoal: ZodNullable<ZodEnum< {
1881
2154
  lose_fat: "lose_fat";
@@ -2376,6 +2649,7 @@ declare const publicRouter: {
2376
2649
  videoAssetId: ZodNullable<ZodString>;
2377
2650
  headerImageId: ZodNullable<ZodString>;
2378
2651
  headerImageUrl: ZodNullable<ZodString>;
2652
+ durationMinutes: ZodNullable<ZodNumber>;
2379
2653
  blocks: ZodArray<ZodObject< {
2380
2654
  id: ZodString;
2381
2655
  title: ZodString;
@@ -2448,6 +2722,7 @@ declare const publicRouter: {
2448
2722
  videoAssetId: ZodNullable<ZodString>;
2449
2723
  headerImageId: ZodNullable<ZodString>;
2450
2724
  headerImageUrl: ZodNullable<ZodString>;
2725
+ durationMinutes: ZodNullable<ZodNumber>;
2451
2726
  blocks: ZodArray<ZodObject< {
2452
2727
  id: ZodString;
2453
2728
  title: ZodString;
@@ -2513,6 +2788,7 @@ declare const publicRouter: {
2513
2788
  videoAssetId: ZodNullable<ZodString>;
2514
2789
  headerImageId: ZodNullable<ZodString>;
2515
2790
  headerImageUrl: ZodNullable<ZodString>;
2791
+ durationMinutes: ZodNullable<ZodNumber>;
2516
2792
  blocks: ZodArray<ZodObject< {
2517
2793
  id: ZodString;
2518
2794
  title: ZodString;
@@ -3219,6 +3495,7 @@ declare const publicRouter: {
3219
3495
  }>;
3220
3496
  valueCm: ZodString;
3221
3497
  }, $strip>>;
3498
+ clientRequestId: ZodOptional<ZodString>;
3222
3499
  }, $strip>, ZodObject< {
3223
3500
  data: ZodArray<ZodObject< {
3224
3501
  id: ZodString;
@@ -5431,12 +5708,12 @@ declare const publicRouter: {
5431
5708
  name: ZodString;
5432
5709
  description: ZodString;
5433
5710
  icon: ZodNullable<ZodEnum< {
5711
+ Apple: "Apple";
5434
5712
  Footprints: "Footprints";
5435
5713
  Moon: "Moon";
5436
5714
  GlassWater: "GlassWater";
5437
5715
  Beef: "Beef";
5438
5716
  StretchHorizontal: "StretchHorizontal";
5439
- Apple: "Apple";
5440
5717
  Brain: "Brain";
5441
5718
  Sun: "Sun";
5442
5719
  }>>;
@@ -5588,12 +5865,12 @@ declare const publicRouter: {
5588
5865
  name: ZodString;
5589
5866
  description: ZodString;
5590
5867
  icon: ZodNullable<ZodEnum< {
5868
+ Apple: "Apple";
5591
5869
  Footprints: "Footprints";
5592
5870
  Moon: "Moon";
5593
5871
  GlassWater: "GlassWater";
5594
5872
  Beef: "Beef";
5595
5873
  StretchHorizontal: "StretchHorizontal";
5596
- Apple: "Apple";
5597
5874
  Brain: "Brain";
5598
5875
  Sun: "Sun";
5599
5876
  }>>;
@@ -6267,7 +6544,7 @@ export declare interface RealtimeConnection {
6267
6544
  */
6268
6545
  export declare function refusalData<R extends keyof FytkitRefusalDataByReason>(error: unknown, reason: R): FytkitRefusalDataByReason[R] | null;
6269
6546
 
6270
- declare type RequestSurface = "rpc" | "rest";
6547
+ declare type RequestSurface = "rpc" | "rest" | "internal";
6271
6548
 
6272
6549
  /**
6273
6550
  * Returned by `auth.resendVerification`. `already_verified`: the member's
@@ -6356,6 +6633,10 @@ export declare interface SecurityStatus {
6356
6633
  readonly twoFactorEnabled: boolean;
6357
6634
  readonly totpEnabled: boolean;
6358
6635
  readonly hasBackupCodes: boolean;
6636
+ /** When the password was last set or changed (ISO 8601); null when the account has no password. */
6637
+ readonly passwordChangedAt: string | null;
6638
+ /** Unused backup codes left; null when two-factor is off, 0 when every code is used. */
6639
+ readonly backupCodesRemaining: number | null;
6359
6640
  }
6360
6641
 
6361
6642
  /** Cached session in storage. `expiresAt` is an ISO-8601 string from the server. */
@@ -6418,7 +6699,10 @@ export declare type SignInResult = {
6418
6699
  * `refused`.
6419
6700
  *
6420
6701
  * `onChange` carries no data: read the conversation and the unread count
6421
- * again. Poll while the status is `connecting` or `fallback`. Needs
6702
+ * again. `onCoachTyping`, when given, says who on the coaching team is
6703
+ * writing to the trainee while the channel is live
6704
+ * ({@link CoachTypingState}). Poll while the status is `connecting` or
6705
+ * `fallback`. Needs
6422
6706
  * `@supabase/supabase-js` 2.87.1 or later (see {@link RealtimeClientFactory}).
6423
6707
  *
6424
6708
  * @example
@@ -6451,6 +6735,14 @@ export declare interface SubscribeToMessagesOptions {
6451
6735
  readonly onChange: () => void;
6452
6736
  /** Each change of {@link MessagesRealtimeStatus}, starting with `connecting`. */
6453
6737
  readonly onStatus?: (status: MessagesRealtimeStatus) => void;
6738
+ /**
6739
+ * Each change of {@link CoachTypingState}: someone on the trainee's
6740
+ * coaching team started or stopped writing to them. Optional; never
6741
+ * called before a `typing`. A subscription that ends by itself
6742
+ * (`refused`, or `fallback` for good) reports `idle` first if anyone was
6743
+ * writing; after `unsubscribe()`, nothing is reported.
6744
+ */
6745
+ readonly onCoachTyping?: (typing: CoachTypingState) => void;
6454
6746
  }
6455
6747
 
6456
6748
  /** Pluggable storage so the SDK runs in browser, SSR, and tests. */