@fytkit/sdk 4.0.0 → 4.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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.1.0
8
+
9
+ 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.
10
+
11
+ ### Features
12
+
13
+ * `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)
14
+ * `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)
15
+ * `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)
16
+ * `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)
17
+ * `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)
18
+
19
+ ### Bug Fixes
20
+
21
+ * `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))
22
+
7
23
  ## 4.0.0
8
24
 
9
25
  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,7 @@ 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.requestPasswordReset` / `confirmPasswordReset` / `confirmEmail` / `resendVerification`
273
297
  - Two-factor: `auth.verifyTotp` / `verifyBackupCode` / `requestEmailOtp` / `verifyEmailOtp` (sign-in), `enableTotp` / `verifyTotpSetup` / `disableTwoFactor` / `regenerateBackupCodes` / `getSecurityStatus` (management)
274
298
  - Account security: `auth.changePassword` / `changeEmail` / `confirmEmailChange` / `listSessions` / `revokeSession` / `revokeOtherSessions` / `closeAccount`
275
299
  - 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,33 @@ declare type ClientEntitlementCode = "premium_workouts" | "premium_programs" | "
156
156
 
157
157
  declare type ClientEntitlements = readonly ClientEntitlementCode[] | "all";
158
158
 
159
+ /**
160
+ * How long a staff member stays in {@link CoachTypingState}'s `names` after
161
+ * the last sign that they are writing. The dashboard repeats the sign every
162
+ * 3 seconds while they write.
163
+ */
164
+ export declare const COACH_TYPING_EXPIRY_MS = 8000;
165
+
166
+ /**
167
+ * Whether the trainee's coaching team is writing to them right now, for a
168
+ * "Sam is writing…" line under the conversation:
169
+ * - `idle`: nobody is.
170
+ * - `typing`: the staff members in `names` are, by the display name the
171
+ * conversation shows on their messages, each name once, in the order
172
+ * they started. Show the first, or "Sam and Alex are writing…".
173
+ *
174
+ * A hint, live only: it is heard only while the status is `live`, and goes
175
+ * back to `idle` when the channel is lost. A name that hears nothing more
176
+ * for 8 seconds ({@link COACH_TYPING_EXPIRY_MS}) drops out by itself, so a
177
+ * lost "stopped" never leaves it showing. While polling it stays `idle`.
178
+ */
179
+ export declare type CoachTypingState = {
180
+ readonly kind: "idle";
181
+ } | {
182
+ readonly kind: "typing";
183
+ readonly names: readonly string[];
184
+ };
185
+
159
186
  export declare function createFytkitClient(options: FytkitClientOptions): FytkitClient;
160
187
 
161
188
  /**
@@ -169,7 +196,7 @@ export declare function createMemoryStorageAdapter(): TokenStorage;
169
196
 
170
197
  declare interface DeveloperAppRequestContext {
171
198
  readonly headers: Headers;
172
- readonly surface?: RequestSurface;
199
+ readonly surface: RequestSurface;
173
200
  }
174
201
 
175
202
  /** The full SDK client surface. `auth.*` plus the public-router slices, minus auth (those live on `client.auth`). */
@@ -236,6 +263,8 @@ export declare interface FytkitClient {
236
263
  verifyTotpSetup: (input: {
237
264
  code: string;
238
265
  }) => Promise<void>;
266
+ /** Turns off 2FA and signs out every other device. The server rotates
267
+ * this session; the SDK swaps its stored token transparently. */
239
268
  disableTwoFactor: (input: {
240
269
  password: string;
241
270
  }) => Promise<void>;
@@ -1059,6 +1088,7 @@ declare const publicRouter: {
1059
1088
  }, $strip>, ZodObject< {
1060
1089
  data: ZodObject< {
1061
1090
  success: ZodLiteral<true>;
1091
+ accessToken: ZodString;
1062
1092
  }, $strip>;
1063
1093
  }, $strip>, Record<never, never>, Record<never, never>>;
1064
1094
  regenerateBackupCodes: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, {
@@ -1179,6 +1209,8 @@ declare const publicRouter: {
1179
1209
  twoFactorEnabled: ZodBoolean;
1180
1210
  totpEnabled: ZodBoolean;
1181
1211
  hasBackupCodes: ZodBoolean;
1212
+ passwordChangedAt: ZodNullable<ZodString>;
1213
+ backupCodesRemaining: ZodNullable<ZodNumber>;
1182
1214
  }, $strip>;
1183
1215
  }, $strip>, Record<never, never>, Record<never, never>>;
1184
1216
  changePassword: DecoratedProcedure<MergedInitialContext<DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext & Record<never, never>, DeveloperAppRequestContext>, MergedCurrentContext<DeveloperAppRequestContext, {
@@ -1830,6 +1862,10 @@ declare const publicRouter: {
1830
1862
  lb: "lb";
1831
1863
  kg: "kg";
1832
1864
  }>;
1865
+ lengthUnit: ZodEnum< {
1866
+ in: "in";
1867
+ cm: "cm";
1868
+ }>;
1833
1869
  timezone: ZodNullable<ZodString>;
1834
1870
  fitnessGoal: ZodNullable<ZodEnum< {
1835
1871
  lose_fat: "lose_fat";
@@ -1857,6 +1893,10 @@ declare const publicRouter: {
1857
1893
  lb: "lb";
1858
1894
  kg: "kg";
1859
1895
  }>>;
1896
+ lengthUnit: ZodOptional<ZodEnum< {
1897
+ in: "in";
1898
+ cm: "cm";
1899
+ }>>;
1860
1900
  fitnessGoal: ZodOptional<ZodNullable<ZodEnum< {
1861
1901
  lose_fat: "lose_fat";
1862
1902
  build_muscle: "build_muscle";
@@ -1876,6 +1916,10 @@ declare const publicRouter: {
1876
1916
  lb: "lb";
1877
1917
  kg: "kg";
1878
1918
  }>;
1919
+ lengthUnit: ZodEnum< {
1920
+ in: "in";
1921
+ cm: "cm";
1922
+ }>;
1879
1923
  timezone: ZodNullable<ZodString>;
1880
1924
  fitnessGoal: ZodNullable<ZodEnum< {
1881
1925
  lose_fat: "lose_fat";
@@ -2376,6 +2420,7 @@ declare const publicRouter: {
2376
2420
  videoAssetId: ZodNullable<ZodString>;
2377
2421
  headerImageId: ZodNullable<ZodString>;
2378
2422
  headerImageUrl: ZodNullable<ZodString>;
2423
+ durationMinutes: ZodNullable<ZodNumber>;
2379
2424
  blocks: ZodArray<ZodObject< {
2380
2425
  id: ZodString;
2381
2426
  title: ZodString;
@@ -2448,6 +2493,7 @@ declare const publicRouter: {
2448
2493
  videoAssetId: ZodNullable<ZodString>;
2449
2494
  headerImageId: ZodNullable<ZodString>;
2450
2495
  headerImageUrl: ZodNullable<ZodString>;
2496
+ durationMinutes: ZodNullable<ZodNumber>;
2451
2497
  blocks: ZodArray<ZodObject< {
2452
2498
  id: ZodString;
2453
2499
  title: ZodString;
@@ -2513,6 +2559,7 @@ declare const publicRouter: {
2513
2559
  videoAssetId: ZodNullable<ZodString>;
2514
2560
  headerImageId: ZodNullable<ZodString>;
2515
2561
  headerImageUrl: ZodNullable<ZodString>;
2562
+ durationMinutes: ZodNullable<ZodNumber>;
2516
2563
  blocks: ZodArray<ZodObject< {
2517
2564
  id: ZodString;
2518
2565
  title: ZodString;
@@ -3219,6 +3266,7 @@ declare const publicRouter: {
3219
3266
  }>;
3220
3267
  valueCm: ZodString;
3221
3268
  }, $strip>>;
3269
+ clientRequestId: ZodOptional<ZodString>;
3222
3270
  }, $strip>, ZodObject< {
3223
3271
  data: ZodArray<ZodObject< {
3224
3272
  id: ZodString;
@@ -6267,7 +6315,7 @@ export declare interface RealtimeConnection {
6267
6315
  */
6268
6316
  export declare function refusalData<R extends keyof FytkitRefusalDataByReason>(error: unknown, reason: R): FytkitRefusalDataByReason[R] | null;
6269
6317
 
6270
- declare type RequestSurface = "rpc" | "rest";
6318
+ declare type RequestSurface = "rpc" | "rest" | "internal";
6271
6319
 
6272
6320
  /**
6273
6321
  * Returned by `auth.resendVerification`. `already_verified`: the member's
@@ -6356,6 +6404,10 @@ export declare interface SecurityStatus {
6356
6404
  readonly twoFactorEnabled: boolean;
6357
6405
  readonly totpEnabled: boolean;
6358
6406
  readonly hasBackupCodes: boolean;
6407
+ /** When the password was last set or changed (ISO 8601); null when the account has no password. */
6408
+ readonly passwordChangedAt: string | null;
6409
+ /** Unused backup codes left; null when two-factor is off, 0 when every code is used. */
6410
+ readonly backupCodesRemaining: number | null;
6359
6411
  }
6360
6412
 
6361
6413
  /** Cached session in storage. `expiresAt` is an ISO-8601 string from the server. */
@@ -6418,7 +6470,10 @@ export declare type SignInResult = {
6418
6470
  * `refused`.
6419
6471
  *
6420
6472
  * `onChange` carries no data: read the conversation and the unread count
6421
- * again. Poll while the status is `connecting` or `fallback`. Needs
6473
+ * again. `onCoachTyping`, when given, says who on the coaching team is
6474
+ * writing to the trainee while the channel is live
6475
+ * ({@link CoachTypingState}). Poll while the status is `connecting` or
6476
+ * `fallback`. Needs
6422
6477
  * `@supabase/supabase-js` 2.87.1 or later (see {@link RealtimeClientFactory}).
6423
6478
  *
6424
6479
  * @example
@@ -6451,6 +6506,14 @@ export declare interface SubscribeToMessagesOptions {
6451
6506
  readonly onChange: () => void;
6452
6507
  /** Each change of {@link MessagesRealtimeStatus}, starting with `connecting`. */
6453
6508
  readonly onStatus?: (status: MessagesRealtimeStatus) => void;
6509
+ /**
6510
+ * Each change of {@link CoachTypingState}: someone on the trainee's
6511
+ * coaching team started or stopped writing to them. Optional; never
6512
+ * called before a `typing`. A subscription that ends by itself
6513
+ * (`refused`, or `fallback` for good) reports `idle` first if anyone was
6514
+ * writing; after `unsubscribe()`, nothing is reported.
6515
+ */
6516
+ readonly onCoachTyping?: (typing: CoachTypingState) => void;
6454
6517
  }
6455
6518
 
6456
6519
  /** Pluggable storage so the SDK runs in browser, SSR, and tests. */
package/dist/index.js CHANGED
@@ -316,7 +316,8 @@ function createAuthNamespace(opts) {
316
316
  adoptRotatedToken(result.data.accessToken);
317
317
  };
318
318
  const disableTwoFactor = async (input) => {
319
- await call(() => opts.transport.client.auth.disableTwoFactor(input));
319
+ const result = await call(() => opts.transport.client.auth.disableTwoFactor(input));
320
+ adoptRotatedToken(result.data.accessToken);
320
321
  };
321
322
  const regenerateBackupCodes = async (input) => {
322
323
  const result = await call(() => opts.transport.client.auth.regenerateBackupCodes(input));
@@ -754,11 +755,13 @@ function resolvePaymentMode(policy, options) {
754
755
  }
755
756
 
756
757
  // src/realtime.ts
758
+ var COACH_TYPING_EXPIRY_MS = 8e3;
757
759
  var MESSAGES_FALLBACK_POLL_MS = {
758
760
  conversation: 1e4,
759
761
  unreadCount: 6e4
760
762
  };
761
763
  var THREAD_CHANGED_EVENT = "thread_changed";
764
+ var COACH_TYPING_EVENT = "coach_typing";
762
765
  var MEMBER_TOPIC_PREFIX = "member:";
763
766
  var TOKEN_RENEW_BEFORE_MS = 6e4;
764
767
  var TOKEN_MIN_DELAY_MS = 5e3;
@@ -782,6 +785,17 @@ function isFinalError(error) {
782
785
  function isMemberTopic(topic) {
783
786
  return typeof topic === "string" && topic.startsWith(MEMBER_TOPIC_PREFIX) && topic.length > MEMBER_TOPIC_PREFIX.length;
784
787
  }
788
+ function coachTypingSign(message) {
789
+ if (typeof message !== "object" || message === null || !("payload" in message)) return null;
790
+ const payload = message.payload;
791
+ if (typeof payload !== "object" || payload === null) return null;
792
+ if (!("state" in payload) || !("name" in payload)) return null;
793
+ const { state, name } = payload;
794
+ if (state !== "typing" && state !== "stopped") return null;
795
+ if (typeof name !== "string" || name.length === 0) return null;
796
+ const key = "key" in payload && typeof payload.key === "string" ? payload.key : "";
797
+ return { state, name, key: key.length > 0 ? `key:${key}` : `name:${name}` };
798
+ }
785
799
  function isConnection(value) {
786
800
  if (typeof value !== "object" || value === null) return false;
787
801
  if (!("url" in value) || !("anonKey" in value)) return false;
@@ -818,9 +832,45 @@ function subscribeToMessages(client, options) {
818
832
  let rejoinTimer = null;
819
833
  let closures = 0;
820
834
  let stopAuthListener = null;
835
+ const writing = /* @__PURE__ */ new Map();
836
+ let reportedNames = [];
837
+ const reportTyping = () => {
838
+ if (closed) return;
839
+ const names = [...new Set([...writing.values()].map((writer) => writer.name))];
840
+ if (names.length === reportedNames.length && names.every((name, i) => name === reportedNames[i])) {
841
+ return;
842
+ }
843
+ reportedNames = names;
844
+ options.onCoachTyping?.(names.length === 0 ? { kind: "idle" } : { kind: "typing", names });
845
+ };
846
+ const clearTyping = () => {
847
+ if (writing.size === 0) return;
848
+ for (const writer of writing.values()) clearTimeout(writer.expiry);
849
+ writing.clear();
850
+ reportTyping();
851
+ };
852
+ const onTypingSign = (sign) => {
853
+ const known = writing.get(sign.key);
854
+ if (known !== void 0) clearTimeout(known.expiry);
855
+ if (sign.state === "stopped") {
856
+ if (known === void 0) return;
857
+ writing.delete(sign.key);
858
+ reportTyping();
859
+ return;
860
+ }
861
+ writing.set(sign.key, {
862
+ name: sign.name,
863
+ expiry: setTimeout(() => {
864
+ writing.delete(sign.key);
865
+ reportTyping();
866
+ }, COACH_TYPING_EXPIRY_MS)
867
+ });
868
+ reportTyping();
869
+ };
821
870
  const report = (status) => {
822
871
  if (closed || status === lastStatus) return;
823
872
  lastStatus = status;
873
+ if (status !== "live") clearTyping();
824
874
  options.onStatus?.(status);
825
875
  };
826
876
  const clearTimer = () => {
@@ -848,6 +898,7 @@ function subscribeToMessages(client, options) {
848
898
  channel = null;
849
899
  cancelRejoin();
850
900
  closures = 0;
901
+ clearTyping();
851
902
  if (open === null) return;
852
903
  if (joined !== null) Promise.resolve(open.removeChannel(joined)).then(ignore, ignore);
853
904
  open.realtime.disconnect();
@@ -864,8 +915,10 @@ function subscribeToMessages(client, options) {
864
915
  const finish = (status) => {
865
916
  if (closed) return;
866
917
  const changed = status !== lastStatus;
918
+ const wasTyping = writing.size > 0;
867
919
  lastStatus = status;
868
920
  close();
921
+ if (wasTyping) options.onCoachTyping?.({ kind: "idle" });
869
922
  if (changed) options.onStatus?.(status);
870
923
  };
871
924
  const join = (joinTopic, open) => {
@@ -875,6 +928,9 @@ function subscribeToMessages(client, options) {
875
928
  const current = () => !closed && channel === joined;
876
929
  joined.on("broadcast", { event: THREAD_CHANGED_EVENT }, () => {
877
930
  if (current()) options.onChange();
931
+ }).on("broadcast", { event: COACH_TYPING_EVENT }, (message) => {
932
+ const sign = coachTypingSign(message);
933
+ if (sign !== null && current()) onTypingSign(sign);
878
934
  }).subscribe((status) => {
879
935
  if (!current()) return;
880
936
  if (status === "SUBSCRIBED") {
@@ -1061,6 +1117,6 @@ function createFytkitClient(options) {
1061
1117
  };
1062
1118
  }
1063
1119
 
1064
- export { MESSAGES_FALLBACK_POLL_MS, createFytkitClient, createLocalStorageAdapter, createMemoryStorageAdapter, refusalData, resolveJoinCode, resolvePaymentMode, subscribeToMessages };
1120
+ export { COACH_TYPING_EXPIRY_MS, MESSAGES_FALLBACK_POLL_MS, createFytkitClient, createLocalStorageAdapter, createMemoryStorageAdapter, refusalData, resolveJoinCode, resolvePaymentMode, subscribeToMessages };
1065
1121
  //# sourceMappingURL=index.js.map
1066
1122
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/is-record.ts","../src/refusal.ts","../src/error.ts","../src/app.ts","../src/events.ts","../src/auth.ts","../src/base-url.ts","../src/data.ts","../src/storage.ts","../src/retry-after.ts","../src/transport.ts","../src/join.ts","../src/payment-policy.ts","../src/realtime.ts","../src/index.ts"],"names":["cached"],"mappings":"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","file":"index.js","sourcesContent":["// A guard shared by the SDK's readers of loosely typed server bodies\n// (`refusal.ts`, `retry-after.ts`). Internal: not exported from the package.\n\n/** A plain object (not null, not an array) whose keys can be read. */\nexport function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { isRecord } from \"./is-record\";\n\n/**\n * Machine-readable reasons the server attaches to a refusal, as\n * `refusal.reason` on a `FytkitError`. Branch on these rather than on message\n * text. The union is open: the server may add reasons without a breaking SDK\n * release, so always keep a fallback branch.\n *\n * Check-ins (`checkIns.*`):\n * - `\"entitlement_required\"` (`forbidden`, 403): the member's plan lacks\n * `coaching_checkins`. On `checkIns.current` the data also says whether the\n * member is scheduled and has sent check-ins before (`member`).\n * - `\"already_submitted\"` (`conflict`, 409): this period already has a\n * check-in, `checkinId`; open or edit it instead.\n * - `\"reviewed\"` (`conflict`, 409): the coach reviewed it, so it is locked.\n * - `\"questions_changed\"` (`conflict`, 409): the coach changed the questions;\n * read `checkIns.current` again.\n * - `\"not_scheduled\"`, `\"not_open\"` (with `opensOn`), `\"not_current_period\"`\n * (with `currentPeriodStart`), `\"invalid_photos\"` (with `uploadIds`)\n * (`server`, 400).\n * - `\"invalid_answers\"` (`validation`, 400): `issues` by question.\n *\n * Messages (`messages.*`):\n * - `\"entitlement_required\"` (`forbidden`, 403): the member may not send now;\n * `eligibility` says whether they need an upgrade (`\"upgrade_required\"`) or\n * should wait for a coach to reply (`\"wait_for_coach\"`).\n * - `\"invalid_attachments\"` (`server`, 400): `uploadIds` that can't be sent.\n * - `\"per_minute_limit\"`, `\"daily_limit\"` (`rate_limited`, 429): with `limit`\n * and `retryAt`, when sending is allowed again.\n *\n * Photo uploads (`checkIns.requestPhotoUpload`,\n * `messages.requestAttachmentUpload`):\n * - `\"too_many_pending_uploads\"` (`rate_limited`, 429): with `limit` and\n * `retryAt`.\n *\n * Subscription checkout (`subscription.createCheckout`):\n * - `\"already_subscribed\"`, `\"payment_issue\"` (`conflict`, 409).\n * - `\"max_clients\"` (`forbidden`, 403): the studio is full and takes no new\n * paying members. The server names this one by its quota (`quotaCode`)\n * and sends nothing else with it; the SDK reports it as a reason like the\n * rest, with no data.\n *\n * Billing portal (`subscription.createPortal`):\n * - `\"no_billing_account\"` (`not_found`, 404): the member never started a\n * paid plan here; hide \"Manage billing\".\n * - `\"no_connected_account\"`, `\"portal_unavailable\"` (`server`, 400): the\n * studio's billing isn't set up; send the member to their coach.\n *\n * Habits (`habits.*`):\n * - `\"entitlement_required\"` (`forbidden`, 403): the member's plan lacks\n * `coaching_habits`. On `habits.today` the data also says whether their\n * coach gave them a habit that hasn't ended (`member.assigned`): show an\n * upgrade prompt when it is true.\n *\n * Weigh-ins and measurements (`bodyMetrics.update`, `bodyMetrics.delete`,\n * `bodyMeasurements.delete`):\n * - `\"staff_entered\"` (`conflict`, 409): their coach entered it; it is the\n * coach's to change or remove.\n * - `\"checkin_sourced\"` (`conflict`, 409, `bodyMetrics.update`): a check-in\n * recorded it; change it by editing the check-in.\n *\n * Progress photos (`progressPhotos.*`):\n * - `\"consent_required\"` (`conflict`, 409): progress photos are off.\n * - `\"photo_limit_reached\"` (`conflict`, 409): the timeline holds `limit`\n * photos.\n * - `\"invalid_uploads\"` (`server`, 400): `uploadIds` that can't be added.\n * - `\"poses_differ\"` (`server`, 400): compare two photos of the same pose.\n *\n * Health (`health.submitIntake`, `health.reportInjury`):\n * - `\"consent_required\"` (`conflict`, 409, `reportInjury`): no consent on\n * file at `currentVersion`; send the report again with consent.\n * - `\"consent_outdated\"` (`conflict`, 409): the consent wording changed to\n * `currentVersion`; show it again.\n * - `\"too_many_pending\"` (`conflict`, 409, `reportInjury`): `limit` reports\n * already wait for the coach.\n * - `\"questions_changed\"` (`conflict`, 409), `\"invalid_answers\"`\n * (`validation`, 400) on `submitIntake`, as on check-ins.\n *\n * Join codes (`resolveJoinCode`):\n * - `\"join_code_inactive\"` (`not_found`, 404): no studio has the code, or\n * the studio replaced it; ask the member to get a new one from their coach.\n */\nexport type FytkitRefusalReason =\n | \"entitlement_required\"\n | \"already_submitted\"\n | \"reviewed\"\n | \"questions_changed\"\n | \"not_scheduled\"\n | \"not_open\"\n | \"not_current_period\"\n | \"invalid_answers\"\n | \"invalid_photos\"\n | \"invalid_attachments\"\n | \"per_minute_limit\"\n | \"daily_limit\"\n | \"too_many_pending_uploads\"\n | \"already_subscribed\"\n | \"payment_issue\"\n | \"max_clients\"\n | \"no_billing_account\"\n | \"no_connected_account\"\n | \"portal_unavailable\"\n | \"staff_entered\"\n | \"checkin_sourced\"\n | \"consent_required\"\n | \"consent_outdated\"\n | \"too_many_pending\"\n | \"photo_limit_reached\"\n | \"invalid_uploads\"\n | \"poses_differ\"\n | \"join_code_inactive\"\n // Keeps the union open (and autocompletion for the known reasons).\n | (string & {});\n\n/**\n * The server's structured refusal on a `FytkitError`: why it said no and\n * what it sent with that. Present only when the server named a reason.\n */\nexport interface FytkitRefusal {\n /** The machine-readable reason (see `FytkitRefusalReason`). */\n readonly reason: FytkitRefusalReason;\n /** The rest of the error's data, without `reason`. */\n readonly data: Readonly<Record<string, unknown>>;\n}\n\n/** When a limited action is allowed again, as the 429 refusals send it. */\nexport interface FytkitRefusalRetryAt {\n /** The limit that was reached. */\n readonly limit: number;\n /** ISO 8601 instant when the limit lifts. */\n readonly retryAt: string;\n /** Milliseconds from the refusal until `retryAt`; also the error's `retryAfter`. */\n readonly retryAfterMs?: number;\n}\n\n/**\n * What each known reason's `refusal.data` holds, for `refusalData`. The\n * server documents these shapes; the SDK does not check them at runtime, as\n * it trusts every other response shape. An empty record is a refusal that\n * sends nothing beyond its reason.\n */\nexport interface FytkitRefusalDataByReason {\n /** A coaching feature the member's plan lacks. */\n readonly entitlement_required:\n | {\n readonly entitlement: \"coaching_checkins\";\n /** On `checkIns.current` only: whether to offer check-ins at all. */\n readonly member?: { readonly scheduled: boolean; readonly hasCheckins: boolean };\n }\n | {\n readonly entitlement: \"coaching_messages\";\n readonly eligibility: \"upgrade_required\" | \"wait_for_coach\";\n }\n | {\n readonly entitlement: \"coaching_habits\";\n /** On `habits.today` only: whether their coach gave them a habit that hasn't ended. */\n readonly member?: { readonly assigned: boolean };\n };\n /** The check-in already sent for this period. */\n readonly already_submitted: { readonly checkinId: string; readonly submittedAt: string };\n /** The check-in the coach reviewed, so it is locked. */\n readonly reviewed: { readonly checkinId: string };\n /** The coach changed the questions; read `checkIns.current` again. */\n readonly questions_changed: Readonly<Record<string, never>>;\n /** The member has no check-in schedule. */\n readonly not_scheduled: Readonly<Record<string, never>>;\n /** `opensOn` is a calendar date, `YYYY-MM-DD`. */\n readonly not_open: { readonly opensOn: string };\n /** `currentPeriodStart` is a calendar date, `YYYY-MM-DD`. */\n readonly not_current_period: { readonly currentPeriodStart: string };\n /** Answers that don't fit their questions, by question (null: the whole check-in). */\n readonly invalid_answers: {\n readonly issues: readonly { readonly questionId: string | null; readonly message: string }[];\n };\n /** Photos that can't be added to the check-in; upload them again. */\n readonly invalid_photos: { readonly uploadIds: readonly string[] };\n /** Photos that can't be sent with the message; upload them again. */\n readonly invalid_attachments: { readonly uploadIds: readonly string[] };\n /** 20 messages a minute. */\n readonly per_minute_limit: FytkitRefusalRetryAt;\n /** 300 messages a day, until midnight in the studio's time zone. */\n readonly daily_limit: FytkitRefusalRetryAt;\n /** 20 photos requested but not yet sent. */\n readonly too_many_pending_uploads: FytkitRefusalRetryAt;\n /** The member already has an active subscription. */\n readonly already_subscribed: Readonly<Record<string, never>>;\n /** The member's last payment failed and the old subscription is still live. */\n readonly payment_issue: Readonly<Record<string, never>>;\n /** The studio is full. Nothing beyond the reason: the refusal carries no figures. */\n readonly max_clients: Readonly<Record<string, never>>;\n /** The member has no billing account at this studio. */\n readonly no_billing_account: Readonly<Record<string, never>>;\n /** The studio has no connected payments account. */\n readonly no_connected_account: Readonly<Record<string, never>>;\n /** The studio hasn't set up its billing portal. */\n readonly portal_unavailable: Readonly<Record<string, never>>;\n /** Their coach entered this weigh-in or measurement. */\n readonly staff_entered: Readonly<Record<string, never>>;\n /** A check-in recorded this weigh-in; edit the check-in instead. */\n readonly checkin_sourced: Readonly<Record<string, never>>;\n /**\n * Health: no consent on file at `currentVersion` (`health.reportInjury`).\n * Progress photos: they are off, and nothing else is sent.\n */\n readonly consent_required: { readonly currentVersion?: string };\n /** The health-data consent wording changed; `currentVersion` is the current one. */\n readonly consent_outdated: { readonly currentVersion: string };\n /** `limit` injury reports already wait for the coach. */\n readonly too_many_pending: { readonly limit: number };\n /** The progress-photo timeline holds `limit` photos. */\n readonly photo_limit_reached: { readonly limit: number };\n /** Photo uploads that can't be added; upload them again. */\n readonly invalid_uploads: { readonly uploadIds: readonly string[] };\n /** Two photos of different poses can't be compared. */\n readonly poses_differ: Readonly<Record<string, never>>;\n /** No studio has the join code, or the studio replaced it. */\n readonly join_code_inactive: Readonly<Record<string, never>>;\n}\n\n/**\n * The data of `error`'s refusal when its reason is `reason`, typed for that\n * reason; null when `error` is not a `FytkitError`, carries no refusal, or\n * was refused for another reason.\n *\n * ```ts\n * catch (err) {\n * const sent = refusalData(err, \"already_submitted\");\n * if (sent) return openCheckIn(sent.checkinId);\n * }\n * ```\n */\nexport function refusalData<R extends keyof FytkitRefusalDataByReason>(\n error: unknown,\n reason: R,\n): FytkitRefusalDataByReason[R] | null {\n if (!isRecord(error) || typeof error.kind !== \"string\") return null;\n const refusal = error.refusal;\n if (!isRecord(refusal) || refusal.reason !== reason || !isRecord(refusal.data)) return null;\n // The server's documented shape for this reason (see FytkitRefusalDataByReason).\n return refusal.data as FytkitRefusalDataByReason[R];\n}\n\n/**\n * The member cap is the one refusal the server names by its quota\n * (`quotaCode`) rather than a `reason`: a studio that takes no new members\n * refuses a member's checkout with it. The refusal carries no figures, only\n * `quotaCode`, and the SDK passes the reason alone: should anything else ever\n * arrive beside it, it is dropped rather than handed to the app.\n */\nconst MEMBER_CAP_QUOTA = \"max_clients\";\n\n/**\n * The refusal in an error's `data`, as a spread-able object so a kind only\n * gains the key when the server named a reason (existing `toEqual` shapes\n * stay intact): the reason, and the rest of the data without it.\n */\nexport function pickRefusal(data: unknown): { readonly refusal?: FytkitRefusal } {\n if (!isRecord(data)) return {};\n const { reason, ...rest } = data;\n if (typeof reason === \"string\" && reason.length > 0) return { refusal: { reason, data: rest } };\n if (data.quotaCode === MEMBER_CAP_QUOTA)\n return { refusal: { reason: MEMBER_CAP_QUOTA, data: {} } };\n return {};\n}\n","import { ORPCError } from \"@orpc/client\";\nimport { pickRefusal } from \"./refusal\";\nimport type { FytkitError } from \"./types\";\n\ninterface MaybeIssuesData {\n issues?: readonly unknown[];\n}\ninterface MaybeRetryAfterData {\n retryAfterMs?: number;\n}\n\nfunction pickIssues(data: unknown): readonly unknown[] | null {\n if (!data || typeof data !== \"object\") return null;\n const issues = (data as MaybeIssuesData).issues;\n return Array.isArray(issues) ? issues : null;\n}\n\nfunction pickRetryAfter(data: unknown): number {\n if (!data || typeof data !== \"object\") return 0;\n const raw = (data as MaybeRetryAfterData).retryAfterMs;\n return typeof raw === \"number\" && raw >= 0 ? raw : 0;\n}\n\n/**\n * A non-empty string field of the error's `data`, when the server sent one:\n * `code` (the machine-readable reason, e.g. \"invalid_password\") or\n * `enrollmentId` (the existing enrolment on \"Already enrolled\"). Returned as\n * a spread-able object so kinds only gain the key when there is a value —\n * existing `toEqual` shapes stay intact.\n */\nfunction pickStringField<K extends \"code\" | \"enrollmentId\">(\n data: unknown,\n key: K,\n): { [P in K]?: string } {\n if (!data || typeof data !== \"object\") return {};\n const raw = (data as Partial<Record<K, unknown>>)[key];\n return typeof raw === \"string\" && raw.length > 0 ? ({ [key]: raw } as { [P in K]?: string }) : {};\n}\n\n/**\n * Map any error thrown by `@orpc/client` (or the underlying fetch) into the\n * SDK's `FytkitError` discriminated union. The SDK never re-throws raw orpc\n * or network errors at the public surface.\n *\n * A refusal the server named (`data.reason`, see `FytkitRefusal`) rides along\n * as `refusal` on the kinds that can carry one, beside the fields each kind\n * already had: `forbidden.reason` stays the message text.\n */\nexport function normalizeError(err: unknown): FytkitError {\n // Network failures from fetch surface as TypeError.\n if (err instanceof TypeError) return { kind: \"network\" };\n\n if (err instanceof ORPCError) {\n const code = err.code;\n const status = err.status;\n const data: unknown = err.data;\n const message = err.message;\n\n switch (code) {\n case \"UNAUTHORIZED\":\n // Carry the server's reason (e.g. \"Invalid credentials\" on a failed\n // sign-in) so the consumer can show it instead of a generic\n // 'session expired' copy. The synthetic no-session case in auth.ts\n // is built directly and stays message-less.\n return { kind: \"auth_required\", message };\n case \"FORBIDDEN\":\n return {\n kind: \"forbidden\",\n reason: message || \"Forbidden\",\n ...pickStringField(data, \"code\"),\n ...pickRefusal(data),\n };\n case \"PAYMENT_REQUIRED\":\n return { kind: \"payment_required\", message: message || \"Payment Required\" };\n case \"CONFLICT\":\n return {\n kind: \"conflict\",\n message: message || \"Conflict\",\n ...pickStringField(data, \"enrollmentId\"),\n ...pickRefusal(data),\n };\n case \"NOT_FOUND\":\n return { kind: \"not_found\", ...pickRefusal(data) };\n case \"TOO_MANY_REQUESTS\":\n return { kind: \"rate_limited\", retryAfter: pickRetryAfter(data), ...pickRefusal(data) };\n case \"BAD_REQUEST\":\n case \"UNPROCESSABLE_CONTENT\": {\n const issues = pickIssues(data);\n if (issues) return { kind: \"validation\", issues, ...pickRefusal(data) };\n // No structured issues — still carry the server's explanation (and\n // its code, e.g. \"invalid_password\") so it isn't dropped into a bare\n // status code.\n return {\n kind: \"server\",\n status,\n message,\n ...pickStringField(data, \"code\"),\n ...pickRefusal(data),\n };\n }\n default:\n return {\n kind: \"server\",\n status,\n message,\n ...pickStringField(data, \"code\"),\n ...pickRefusal(data),\n };\n }\n }\n\n return { kind: \"server\", status: 0 };\n}\n","// `app.bootstrap` (#535): Fytkit Coach's one launch read. Everything the app\n// paints and gates at launch, for the studio the client id names: its name\n// and logo, the app theme, feature overrides, the minimum app version, the\n// legal links, the Realtime settings and the payment policy.\n//\n// The payload is a pure function of the studio's settings and carries a\n// strong ETag. A call with the ETag the app stored last time is answered 304\n// with no body when nothing changed. The typed oRPC client resolves a 304's\n// empty body to `undefined` (its type says it never does), so this wraps the\n// raw procedure in a discriminated union instead of handing that through.\n\nimport type { PublicApi } from \"@fytkit/types\";\nimport { normalizeError } from \"./error\";\nimport type { Transport } from \"./transport\";\nimport type { FytkitError } from \"./types\";\n\n/**\n * Everything the app reads at launch for the studio (`app.bootstrap`'s\n * `data`): `studio` (display name and logo URL), `theme` (the effective\n * template and theme with its palette, derived variants and the oldest app\n * release that can paint it), `themeMinAppVersions`, `home.layout`,\n * `featureOverrides`, `minAppVersion`, `links` (terms, privacy, help),\n * `realtime` (the public Supabase Realtime settings) and `paymentPolicy` (the\n * storefronts where the app may link out to subscribe; read it with\n * `resolvePaymentMode`).\n */\nexport type AppBootstrapData = Awaited<ReturnType<PublicApi[\"app\"][\"bootstrap\"]>>[\"data\"];\n\n/** Options for `app.bootstrap`. */\nexport interface AppBootstrapOptions {\n /**\n * The `etag` of the last bootstrap the app kept. When the studio's\n * settings are unchanged the server answers 304 and the call resolves to\n * `{ status: \"unchanged\" }`, so keep using the data stored with it. Omit it\n * (or pass `null`) on a first launch, or when no data is stored.\n */\n readonly etag?: string | null;\n}\n\n/**\n * What `app.bootstrap` resolves to:\n * - `ok`: the current settings, and the `etag` to store beside them and send\n * next time;\n * - `unchanged`: the `etag` you sent is still current (a 304 with no body):\n * the data you stored with it is up to date.\n */\nexport type AppBootstrapResult =\n | { readonly status: \"ok\"; readonly data: AppBootstrapData; readonly etag: string }\n | { readonly status: \"unchanged\"; readonly etag: string };\n\n/** `client.app`: the app's launch read. */\nexport interface AppNamespace {\n /**\n * Reads the studio's app launch settings. Needs the client id alone; a\n * signed-in member's session changes nothing in the payload. Pass the\n * `etag` of the data you stored last time to revalidate it: an unchanged\n * studio answers `{ status: \"unchanged\" }` without a body.\n *\n * Rejects with a `FytkitError`, as every SDK method does (e.g.\n * `{ kind: \"not_found\" }` for a studio that no longer exists,\n * `{ kind: \"network\" }` offline): keep painting the stored data then.\n *\n * ```ts\n * const stored = await loadBootstrap(); // { data, etag } | null\n * const result = await fytkit.app.bootstrap({ etag: stored?.etag });\n * const data = result.status === \"ok\" ? result.data : stored.data;\n * if (result.status === \"ok\") await saveBootstrap(result);\n * ```\n */\n readonly bootstrap: (options?: AppBootstrapOptions) => Promise<AppBootstrapResult>;\n}\n\n/** A bodiless answer (a 304) to a call that sent no `If-None-Match`. */\nconst UNEXPECTED_NOT_MODIFIED: FytkitError = { kind: \"server\", status: 304 };\n\nexport function createAppNamespace(transport: Transport): AppNamespace {\n const bootstrap: AppNamespace[\"bootstrap\"] = async (options = {}) => {\n const etag = options.etag ?? null;\n // A 304 resolves to `undefined` at runtime, whatever the client's type says.\n let output: Awaited<ReturnType<PublicApi[\"app\"][\"bootstrap\"]>> | undefined;\n try {\n output = await transport.client.app.bootstrap(undefined, {\n context: etag === null ? {} : { ifNoneMatch: etag },\n });\n } catch (error) {\n throw normalizeError(error);\n }\n if (output === undefined || output === null) {\n if (etag === null) throw UNEXPECTED_NOT_MODIFIED;\n return { status: \"unchanged\", etag };\n }\n return { status: \"ok\", data: output.data, etag: output.meta.etag };\n };\n return { bootstrap };\n}\n","export interface EventBus<T> {\n readonly on: (listener: (value: T) => void) => () => void;\n readonly emit: (value: T) => void;\n}\n\nexport function createEventBus<T>(): EventBus<T> {\n const listeners = new Set<(value: T) => void>();\n return {\n on: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n emit: (value) => {\n for (const listener of listeners) {\n try {\n listener(value);\n } catch (err) {\n // A buggy listener must not break siblings or the calling flow.\n console.error(\"[fytkit-sdk] event listener threw\", err);\n }\n }\n },\n };\n}\n","import { normalizeError } from \"./error\";\nimport { createEventBus } from \"./events\";\nimport type { Transport } from \"./transport\";\nimport type {\n AuthState,\n FytkitError,\n ResendVerificationResult,\n SecurityStatus,\n Session,\n SessionInfo,\n SignInResult,\n TokenStorage,\n TotpEnrollment,\n TwoFactorChallenge,\n TwoFactorToken,\n} from \"./types\";\n\nexport interface AuthNamespaceOptions {\n readonly transport: Transport;\n readonly storage: TokenStorage;\n /** Called after the SDK signs out for any reason (manual, refresh failure). */\n readonly onSignedOut?: () => void;\n}\n\nexport interface AuthNamespace {\n readonly signUp: (input: { email: string; password: string; name: string }) => Promise<Session>;\n readonly signIn: (input: { email: string; password: string }) => Promise<SignInResult>;\n readonly signOut: () => Promise<void>;\n readonly getSession: () => Session | null;\n readonly onAuthStateChange: (listener: (state: AuthState) => void) => () => void;\n readonly requestPasswordReset: (input: { email: string }) => Promise<void>;\n readonly confirmPasswordReset: (input: { token: string; password: string }) => Promise<void>;\n readonly confirmEmail: (input: { token: string }) => Promise<void>;\n /**\n * Mail a new verification link to the signed-in member's own address. A\n * bearer call: a 401 refreshes or signs out as usual.\n */\n readonly resendVerification: () => Promise<ResendVerificationResult>;\n readonly verifyTotp: (input: { challenge: TwoFactorChallenge; code: string }) => Promise<Session>;\n readonly verifyBackupCode: (input: {\n challenge: TwoFactorChallenge;\n code: string;\n }) => Promise<Session>;\n readonly requestEmailOtp: (input: { challenge: TwoFactorChallenge }) => Promise<void>;\n readonly verifyEmailOtp: (input: {\n challenge: TwoFactorChallenge;\n code: string;\n }) => Promise<Session>;\n readonly getSecurityStatus: () => Promise<SecurityStatus>;\n readonly enableTotp: (input: { password: string }) => Promise<TotpEnrollment>;\n readonly verifyTotpSetup: (input: { code: string }) => Promise<void>;\n readonly disableTwoFactor: (input: { password: string }) => Promise<void>;\n readonly regenerateBackupCodes: (input: { password: string }) => Promise<readonly string[]>;\n readonly changePassword: (input: {\n currentPassword: string;\n newPassword: string;\n revokeOtherSessions?: boolean;\n }) => Promise<void>;\n readonly changeEmail: (input: { newEmail: string }) => Promise<void>;\n /**\n * Confirm an email change with the token from the confirmation link, for an\n * app that opens that link itself (Fytkit Coach's app link). No session\n * needed: the emailed token is the proof.\n */\n readonly confirmEmailChange: (input: { token: string }) => Promise<void>;\n readonly listSessions: () => Promise<readonly SessionInfo[]>;\n readonly revokeSession: (input: { revokeId: string }) => Promise<void>;\n readonly revokeOtherSessions: () => Promise<void>;\n readonly closeAccount: (input: { confirmEmail: string; password: string }) => Promise<void>;\n /** Internal: force a refresh now. Wired by transport's 401 retry path. */\n readonly _refresh: () => Promise<Session>;\n /** Internal: clear cached session + emit signed_out. */\n readonly _signOutLocal: () => void;\n}\n\n/**\n * Schedule a proactive refresh this many ms before `expiresAt`. The token\n * is a server session (7 days, extended on use), and a 60-second lead time\n * refreshes it transparently in long-running tabs without refreshing\n * needlessly.\n */\nconst PROACTIVE_REFRESH_LEAD_MS = 60_000;\n\n/**\n * When a proactive refresh fails for a reason other than the server rejecting\n * the token (offline, 5xx, rate limit), the session is kept and the timer\n * retries with exponential backoff: 5 s, 10 s, 20 s … capped at 5 min, for\n * at most this many attempts. After that the SDK stops polling; the next\n * request's reactive 401 path takes over if the token has actually lapsed.\n */\nconst REFRESH_RETRY_BASE_MS = 5_000;\nconst REFRESH_RETRY_MAX_MS = 5 * 60_000;\nconst REFRESH_MAX_RETRIES = 6;\n\n/**\n * Only an explicit server rejection of the token (401/403) ends the session.\n * Network failures, 5xx and rate limits say nothing about the token, so\n * signing the member out on them would be wrong — and on a phone that drops\n * signal it signs members out constantly.\n */\nfunction isSessionRejected(err: FytkitError): boolean {\n return err.kind === \"auth_required\" || err.kind === \"forbidden\";\n}\n\nfunction refreshRetryDelay(attempt: number, err: FytkitError): number {\n const backoff = Math.min(REFRESH_RETRY_MAX_MS, REFRESH_RETRY_BASE_MS * 2 ** attempt);\n // Honour the server's rate-limit window when it is longer than our backoff.\n return err.kind === \"rate_limited\" ? Math.max(backoff, err.retryAfter) : backoff;\n}\n\n/**\n * The auth namespace owns the cached session. It is the single writer to\n * `storage`; everything else reads via `getSession`. Calls that mutate\n * the session also emit on the event bus and (re)schedule the proactive\n * refresh timer against the new expiry.\n */\nexport function createAuthNamespace(opts: AuthNamespaceOptions): AuthNamespace {\n const bus = createEventBus<AuthState>();\n let cached: Session | null = null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n // Forward declaration so scheduleRefresh can call the same _refresh body\n // the public surface exposes. Assigned at the bottom of the factory.\n let refreshFn: () => Promise<Session> = () => {\n throw new Error(\"refreshFn called before initialization\");\n };\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n // `setTimeout` clamps any delay > 2^31-1 ms (~24.8 days) to 1 ms and\n // prints a Node deprecation warning. Sessions with an exotic far-future\n // expiry (test fixtures, mis-configured server) would otherwise fire the\n // timer instantly; cap the delay to the safe upper bound so the timer\n // simply re-arms on its next firing.\n const MAX_TIMEOUT_MS = 2_147_483_647;\n\n // `attempt` counts consecutive failed proactive refreshes; 0 for the\n // expiry-driven firing, 1+ for backoff retries.\n function armRefreshTimer(delay: number, attempt: number): void {\n clearTimer();\n timer = setTimeout(() => {\n timer = null;\n void refreshFn().catch((err: FytkitError) => {\n // A rejected token already cleared the session inside _refresh.\n // Otherwise keep it and retry — unless something re-armed the timer\n // meanwhile (a sign-in, a rotated token) or we are out of attempts.\n if (cached === null || timer !== null || attempt >= REFRESH_MAX_RETRIES) return;\n armRefreshTimer(refreshRetryDelay(attempt, err), attempt + 1);\n });\n }, delay);\n }\n\n function scheduleRefresh(session: Session): void {\n clearTimer();\n const expiresMs = new Date(session.expiresAt).getTime();\n if (Number.isNaN(expiresMs)) return; // bad date — bail; reactive 401 path will handle it\n const rawDelay = expiresMs - Date.now() - PROACTIVE_REFRESH_LEAD_MS;\n armRefreshTimer(Math.min(MAX_TIMEOUT_MS, Math.max(0, rawDelay)), 0);\n }\n\n function applySession(session: Session, event: \"signed_in\" | \"session_refreshed\"): Session {\n cached = session;\n void opts.storage.set(session);\n bus.emit({ type: event, session });\n scheduleRefresh(session);\n return session;\n }\n\n function clearSession(emit = true): void {\n cached = null;\n clearTimer();\n void opts.storage.remove();\n if (emit) bus.emit({ type: \"signed_out\" });\n opts.onSignedOut?.();\n }\n\n async function call<T>(thunk: () => Promise<T>): Promise<T> {\n try {\n return await thunk();\n } catch (err) {\n throw normalizeError(err);\n }\n }\n\n // Best-effort hydrate at construction. If the storage already holds a\n // session (warm reload, second tab), restore it and arm the timer.\n void Promise.resolve(opts.storage.get()).then((s) => {\n if (s) {\n cached = s;\n scheduleRefresh(s);\n }\n });\n\n const signUp: AuthNamespace[\"signUp\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.signUp(input));\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n const signIn: AuthNamespace[\"signIn\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.signIn(input));\n if (result.data.kind === \"two_factor_required\") {\n // Mint the branded token exactly once at the network boundary.\n const token = result.data.twoFactorToken as TwoFactorToken;\n return {\n kind: \"two_factor_required\",\n challenge: { token, availableMethods: result.data.availableMethods },\n };\n }\n if (result.data.kind === \"signed_in\") {\n const { user, session } = result.data;\n const cached = applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n return { kind: \"signed_in\", session: cached };\n }\n // Exhaustiveness guard — a future server-side variant must not silently fall through.\n const _exhaustive: never = result.data;\n throw _exhaustive;\n };\n\n const signOut: AuthNamespace[\"signOut\"] = async () => {\n const current = cached;\n if (!current) return;\n try {\n await opts.transport.client.auth.signOut({ token: current.accessToken });\n } catch (err) {\n // Even if the server rejects (e.g. token already revoked), clear\n // the local cache so the user is signed out client-side.\n void err;\n }\n clearSession();\n };\n\n const getSession: AuthNamespace[\"getSession\"] = () => cached;\n const onAuthStateChange: AuthNamespace[\"onAuthStateChange\"] = (listener) => bus.on(listener);\n\n const requestPasswordReset: AuthNamespace[\"requestPasswordReset\"] = async (input) => {\n await call(() => opts.transport.client.auth.requestPasswordReset(input));\n };\n const confirmPasswordReset: AuthNamespace[\"confirmPasswordReset\"] = async (input) => {\n await call(() => opts.transport.client.auth.confirmPasswordReset(input));\n };\n const confirmEmail: AuthNamespace[\"confirmEmail\"] = async (input) => {\n await call(() => opts.transport.client.auth.confirmEmail(input));\n };\n\n const resendVerification: AuthNamespace[\"resendVerification\"] = async () => {\n const result = await call(() => opts.transport.client.auth.resendVerification({}));\n return result.data;\n };\n\n // --- Two-factor sign-in completion -----------------------------------------\n //\n // Each consumes the challenge minted by `signIn` and, on success, caches the\n // issued session exactly like the signed_in branch of `signIn`.\n\n const verifyTotp: AuthNamespace[\"verifyTotp\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.verifyTotp({ token: input.challenge.token, code: input.code }),\n );\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n const verifyBackupCode: AuthNamespace[\"verifyBackupCode\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.verifyBackupCode({\n token: input.challenge.token,\n code: input.code,\n }),\n );\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n const requestEmailOtp: AuthNamespace[\"requestEmailOtp\"] = async (input) => {\n await call(() => opts.transport.client.auth.requestEmailOtp({ token: input.challenge.token }));\n };\n\n const verifyEmailOtp: AuthNamespace[\"verifyEmailOtp\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.verifyEmailOtp({ token: input.challenge.token, code: input.code }),\n );\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n /**\n * The server rotated the calling session and returned the replacement token\n * (TOTP-setup verification and password change with revokeOtherSessions do\n * this). Swap the cached token immediately — the old bearer is already dead,\n * so any request between now and the swap would 401. `expiresAt` is carried\n * over from the old session; it is at worst *earlier* than the new session's\n * real expiry, so the proactive refresh timer fires early and corrects it.\n */\n function adoptRotatedToken(accessToken: string): void {\n const current = cached;\n if (!current || !accessToken || accessToken === current.accessToken) return;\n applySession({ ...current, accessToken }, \"session_refreshed\");\n }\n\n const getSecurityStatus: AuthNamespace[\"getSecurityStatus\"] = async () => {\n const result = await call(() => opts.transport.client.auth.getSecurityStatus());\n return result.data;\n };\n\n const enableTotp: AuthNamespace[\"enableTotp\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.enableTotp(input));\n return result.data;\n };\n\n const verifyTotpSetup: AuthNamespace[\"verifyTotpSetup\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.verifyTotpSetup(input));\n adoptRotatedToken(result.data.accessToken);\n };\n\n const disableTwoFactor: AuthNamespace[\"disableTwoFactor\"] = async (input) => {\n await call(() => opts.transport.client.auth.disableTwoFactor(input));\n };\n\n const regenerateBackupCodes: AuthNamespace[\"regenerateBackupCodes\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.regenerateBackupCodes(input));\n return result.data.backupCodes;\n };\n\n // --- Account security -------------------------------------------------------\n\n const changePassword: AuthNamespace[\"changePassword\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.changePassword({\n currentPassword: input.currentPassword,\n newPassword: input.newPassword,\n revokeOtherSessions: input.revokeOtherSessions,\n }),\n );\n adoptRotatedToken(result.data.accessToken);\n };\n\n const changeEmail: AuthNamespace[\"changeEmail\"] = async (input) => {\n await call(() => opts.transport.client.auth.changeEmail(input));\n };\n\n const confirmEmailChange: AuthNamespace[\"confirmEmailChange\"] = async (input) => {\n await call(() => opts.transport.client.auth.confirmEmailChange(input));\n };\n\n const listSessions: AuthNamespace[\"listSessions\"] = async () => {\n const result = await call(() => opts.transport.client.auth.listSessions());\n return result.data;\n };\n\n const revokeSession: AuthNamespace[\"revokeSession\"] = async (input) => {\n await call(() => opts.transport.client.auth.revokeSession(input));\n };\n\n const revokeOtherSessions: AuthNamespace[\"revokeOtherSessions\"] = async () => {\n await call(() => opts.transport.client.auth.revokeOtherSessions());\n };\n\n // The server deletes the login, so the cached bearer is dead the moment\n // this resolves — drop it locally (emits signed_out). On failure (wrong\n // password, network) the session is untouched and the error propagates.\n const closeAccount: AuthNamespace[\"closeAccount\"] = async (input) => {\n await call(() =>\n opts.transport.client.auth.closeAccount({\n confirmEmail: input.confirmEmail,\n password: input.password,\n }),\n );\n clearSession();\n };\n\n const _refresh: AuthNamespace[\"_refresh\"] = async () => {\n // Re-read storage as a fallback so a session written after\n // construction (e.g. on tab restore or in tests) is honoured.\n let current = cached;\n if (!current) {\n const fromStorage = await Promise.resolve(opts.storage.get());\n if (fromStorage) {\n cached = fromStorage;\n current = fromStorage;\n }\n }\n if (!current) {\n const err: FytkitError = { kind: \"auth_required\" };\n throw err;\n }\n try {\n const result = await opts.transport.client.auth.refresh({ token: current.accessToken });\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"session_refreshed\",\n );\n } catch (err) {\n const normalized = normalizeError(err);\n // Sign out only when the server actually rejected the token. A network\n // or server failure leaves the session in place for a later retry.\n if (isSessionRejected(normalized)) clearSession();\n throw normalized;\n }\n };\n\n const _signOutLocal: AuthNamespace[\"_signOutLocal\"] = () => clearSession();\n\n refreshFn = _refresh;\n\n return {\n signUp,\n signIn,\n signOut,\n getSession,\n onAuthStateChange,\n requestPasswordReset,\n confirmPasswordReset,\n confirmEmail,\n resendVerification,\n verifyTotp,\n verifyBackupCode,\n requestEmailOtp,\n verifyEmailOtp,\n getSecurityStatus,\n enableTotp,\n verifyTotpSetup,\n disableTwoFactor,\n regenerateBackupCodes,\n changePassword,\n changeEmail,\n confirmEmailChange,\n listSessions,\n revokeSession,\n revokeOtherSessions,\n closeAccount,\n _refresh,\n _signOutLocal,\n };\n}\n","// Internal: not exported from the package.\n\n/** Where the SDK calls fytkit unless `baseUrl` says otherwise. */\nexport const DEFAULT_BASE_URL = \"https://api.fytkit.com\";\n","import { normalizeError } from \"./error\";\nimport type { Transport } from \"./transport\";\n\n/**\n * Wrap every method on a public-router slice so thrown ORPCErrors become\n * normalised FytkitErrors at the SDK boundary.\n *\n * The slice we receive is an `@orpc/client` Proxy whose own keys are not\n * enumerable — every property lookup is intercepted lazily. We mirror that\n * shape by returning a Proxy of our own that wraps each function on first\n * access and caches the result. This way the SDK never needs to know the\n * method names ahead of time, and the typed shape from the server router\n * flows through unchanged.\n */\nfunction wrapSlice<T extends object>(slice: T): T {\n const cache = new Map<string | symbol, unknown>();\n return new Proxy(slice, {\n get(target, prop, receiver) {\n if (cache.has(prop)) return cache.get(prop);\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== \"function\") {\n // Allow nested namespaces to flow through unchanged (the orpc\n // proxy returns another proxy here, not a plain object).\n return value;\n }\n const wrapped = async (...args: unknown[]) => {\n try {\n return await (value as (...a: unknown[]) => Promise<unknown>)(...args);\n } catch (err) {\n throw normalizeError(err);\n }\n };\n cache.set(prop, wrapped);\n return wrapped;\n },\n }) as T;\n}\n\nexport interface DataNamespaces {\n readonly workouts: Transport[\"client\"][\"workout\"];\n readonly exercises: Transport[\"client\"][\"exercise\"];\n readonly programs: Transport[\"client\"][\"program\"];\n readonly goals: Transport[\"client\"][\"goal\"];\n readonly equipment: Transport[\"client\"][\"equipment\"];\n readonly recipes: Transport[\"client\"][\"recipe\"];\n readonly nutrition: Transport[\"client\"][\"nutrition\"];\n readonly articles: Transport[\"client\"][\"article\"];\n readonly enrollments: Transport[\"client\"][\"enrollment\"];\n readonly favourites: Transport[\"client\"][\"favourite\"];\n readonly sessionLogs: Transport[\"client\"][\"sessionLog\"];\n readonly progression: Transport[\"client\"][\"progression\"];\n readonly bodyMetrics: Transport[\"client\"][\"bodyMetric\"];\n readonly bodyMeasurements: Transport[\"client\"][\"bodyMeasurement\"];\n readonly progressPhotos: Transport[\"client\"][\"progressPhoto\"];\n readonly checkIns: Transport[\"client\"][\"checkin\"];\n readonly messages: Transport[\"client\"][\"message\"];\n readonly timeline: Transport[\"client\"][\"timeline\"];\n readonly health: Transport[\"client\"][\"health\"];\n readonly habits: Transport[\"client\"][\"habit\"];\n readonly progress: Transport[\"client\"][\"progress\"];\n readonly media: Transport[\"client\"][\"media\"];\n readonly subscription: Transport[\"client\"][\"subscription\"];\n readonly profile: Transport[\"client\"][\"profile\"];\n readonly nutritionLog: Transport[\"client\"][\"nutritionLog\"];\n readonly preferences: Transport[\"client\"][\"preferences\"];\n}\n\nexport function createDataNamespaces(transport: Transport): DataNamespaces {\n const c = transport.client;\n return {\n workouts: wrapSlice(c.workout),\n exercises: wrapSlice(c.exercise),\n programs: wrapSlice(c.program),\n goals: wrapSlice(c.goal),\n equipment: wrapSlice(c.equipment),\n recipes: wrapSlice(c.recipe),\n nutrition: wrapSlice(c.nutrition),\n articles: wrapSlice(c.article),\n enrollments: wrapSlice(c.enrollment),\n favourites: wrapSlice(c.favourite),\n sessionLogs: wrapSlice(c.sessionLog),\n progression: wrapSlice(c.progression),\n bodyMetrics: wrapSlice(c.bodyMetric),\n bodyMeasurements: wrapSlice(c.bodyMeasurement),\n progressPhotos: wrapSlice(c.progressPhoto),\n checkIns: wrapSlice(c.checkin),\n messages: wrapSlice(c.message),\n timeline: wrapSlice(c.timeline),\n health: wrapSlice(c.health),\n habits: wrapSlice(c.habit),\n progress: wrapSlice(c.progress),\n media: wrapSlice(c.media),\n subscription: wrapSlice(c.subscription),\n profile: wrapSlice(c.profile),\n nutritionLog: wrapSlice(c.nutritionLog),\n preferences: wrapSlice(c.preferences),\n };\n}\n","import type { Session, TokenStorage } from \"./types\";\n\nconst STORAGE_KEY_PREFIX = \"fytkit:\";\nconst STORAGE_KEY_SUFFIX = \":tokens\";\n\nfunction keyFor(clientId: string): string {\n return `${STORAGE_KEY_PREFIX}${clientId}${STORAGE_KEY_SUFFIX}`;\n}\n\nfunction isValidSession(value: unknown): value is Session {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.accessToken === \"string\" &&\n typeof v.expiresAt === \"string\" &&\n typeof v.user === \"object\" &&\n v.user !== null &&\n typeof (v.user as { id?: unknown }).id === \"string\" &&\n typeof (v.user as { email?: unknown }).email === \"string\"\n );\n}\n\n/**\n * Default web storage. Reads/writes JSON under `fytkit:{clientId}:tokens`.\n * Silently no-ops when localStorage throws (Safari private mode, SSR).\n */\nexport function createLocalStorageAdapter(clientId: string): TokenStorage {\n const key = keyFor(clientId);\n return {\n get: () => {\n try {\n const raw = globalThis.localStorage?.getItem(key);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n return isValidSession(parsed) ? parsed : null;\n } catch {\n return null;\n }\n },\n set: (session: Session) => {\n try {\n globalThis.localStorage?.setItem(key, JSON.stringify(session));\n } catch {\n // Quota / private-mode / SSR — swallow and let the caller treat the\n // session as in-memory-only for this tab.\n }\n },\n remove: () => {\n try {\n globalThis.localStorage?.removeItem(key);\n } catch {\n // see set() rationale\n }\n },\n };\n}\n\n/** In-memory storage. Useful for tests and SSR. */\nexport function createMemoryStorageAdapter(): TokenStorage {\n let current: Session | null = null;\n return {\n get: () => current,\n set: (session: Session) => {\n current = session;\n },\n remove: () => {\n current = null;\n },\n };\n}\n","// The `Retry-After` fallback for 429s (SDK1, #461).\n//\n// The SDK reads a 429's wait from its error data (`data.retryAfterMs`), which\n// every fytkit 429 sends. A 429 that doesn't carry it (an older server's\n// plain body, a proxy or CDN in front of the API) used to reach an app as\n// `retryAfter: 0`, so a retry loop retried at once. When such a 429 has a\n// `Retry-After` header, the transport writes the header's wait into the\n// error data before oRPC reads the response, so `normalizeError` finds it\n// where it always looks. A hint the data already carries always wins.\n\nimport { isRecord } from \"./is-record\";\n\nconst DELAY_SECONDS = /^\\d+$/;\n\n/**\n * A `Retry-After` value's wait in milliseconds from `now`: delay-seconds\n * (`\"120\"`) or an HTTP date (zero once it has passed). Null when there is no\n * value or it is neither.\n */\nexport function retryAfterHeaderMs(value: string | null, now: number): number | null {\n if (value === null) return null;\n const trimmed = value.trim();\n if (trimmed.length === 0) return null;\n if (DELAY_SECONDS.test(trimmed)) return Number(trimmed) * 1000;\n // Only a date in letters: Date.parse also accepts bare numbers like \"1.5\".\n if (!/[a-z]/i.test(trimmed)) return null;\n const at = Date.parse(trimmed);\n return Number.isNaN(at) ? null : Math.max(0, at - now);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The 429's body with `waitMs` as its error data's `retryAfterMs`, as oRPC's\n * RPC protocol frames it (`{ json: <error> }`); null when the data already\n * carries a hint. A body that isn't an oRPC error becomes the error oRPC\n * would have sent.\n */\nfunction hintedBody(body: unknown, waitMs: number): Readonly<Record<string, unknown>> | null {\n if (isRecord(body) && isRecord(body.json) && typeof body.json.code === \"string\") {\n const error = body.json;\n const data = isRecord(error.data) ? error.data : {};\n if (typeof data.retryAfterMs === \"number\") return null;\n return { ...body, json: { ...error, data: { ...data, retryAfterMs: waitMs } } };\n }\n return {\n json: {\n defined: false,\n code: \"TOO_MANY_REQUESTS\",\n status: 429,\n message: \"Too Many Requests\",\n data: { retryAfterMs: waitMs },\n },\n };\n}\n\n/**\n * `response` with the `Retry-After` header's wait written into its error data\n * when it is a 429 whose data has no `retryAfterMs`; unchanged otherwise.\n */\nexport async function withRetryAfterHint(response: Response, now: number): Promise<Response> {\n if (response.status !== 429) return response;\n const waitMs = retryAfterHeaderMs(response.headers.get(\"retry-after\"), now);\n if (waitMs === null) return response;\n const hinted = hintedBody(parseJson(await response.clone().text()), waitMs);\n if (hinted === null) return response;\n const headers = new Headers(response.headers);\n headers.set(\"content-type\", \"application/json\");\n headers.delete(\"content-length\");\n return new Response(JSON.stringify(hinted), {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n","import type { PublicRouter } from \"@fytkit/types\";\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { RouterClient } from \"@orpc/server\";\nimport { withRetryAfterHint } from \"./retry-after\";\nimport type { Session } from \"./types\";\n\nexport interface CreateTransportOptions {\n readonly baseUrl: string;\n readonly clientId: string;\n readonly getSession: () => Session | null;\n readonly fetch?: typeof fetch;\n}\n\n/**\n * Per-call options the SDK's own wrappers pass to the link. `ifNoneMatch`\n * becomes the request's `If-None-Match` header (`app.bootstrap`'s ETag\n * revalidation, #535); every other call sends none.\n */\nexport interface TransportContext {\n readonly ifNoneMatch?: string;\n}\n\n/** The public router's client, taking {@link TransportContext} per call. */\nexport type TransportClient = RouterClient<PublicRouter, TransportContext>;\n\nexport interface Transport {\n readonly client: TransportClient;\n /** Internal: hook for attachRefreshInterceptor to install a fetch wrapper. */\n readonly _setFetchInterceptor: (interceptor: FetchInterceptor) => void;\n}\n\ntype FetchInterceptor = (\n request: Request,\n next: (req: Request) => Promise<Response>,\n) => Promise<Response>;\n\nexport function createTransport(options: CreateTransportOptions): Transport {\n let interceptor: FetchInterceptor | null = null;\n const baseFetch: typeof fetch = options.fetch ?? fetch.bind(globalThis);\n\n // The fetch handed to RPCLink resolves the interceptor lazily so callers\n // can install one after construction (see attachRefreshInterceptor). A 429\n // whose error data lacks a retry hint gets the `Retry-After` header's wait\n // written in (retry-after.ts), so `rate_limited.retryAfter` reads it.\n const wrappedFetch: typeof fetch = async (input, init) => {\n const request = input instanceof Request ? input : new Request(input, init);\n const next = (req: Request) => baseFetch(req);\n const response = interceptor ? await interceptor(request, next) : await next(request);\n return withRetryAfterHint(response, Date.now());\n };\n\n const link = new RPCLink<TransportContext>({\n url: () => `${options.baseUrl}/rpc`,\n headers: ({ context }) => {\n const session = options.getSession();\n const headers: Record<string, string> = {\n \"X-Fytkit-Client-Id\": options.clientId,\n };\n if (session) {\n headers.Authorization = `Bearer ${session.accessToken}`;\n }\n if (context.ifNoneMatch !== undefined) {\n headers[\"If-None-Match\"] = context.ifNoneMatch;\n }\n return headers;\n },\n fetch: wrappedFetch,\n });\n\n const client: TransportClient = createORPCClient(link);\n return {\n client,\n _setFetchInterceptor: (i) => {\n interceptor = i;\n },\n };\n}\n\n/** Internal marker header — set on the refresh call so we never recurse. */\nconst REFRESH_HEADER = \"X-Fytkit-Refresh\";\n\n/**\n * Procedures that authenticate with credentials in their input (a password,\n * an emailed token, a one-time code or a sign-in challenge), never with the\n * bearer. A 401 from one of them means those credentials were rejected, not\n * that the session is dead, so it passes straight through: no refresh, no\n * replay, no local sign-out. Otherwise a member who mistypes their password\n * while signing in again (after `reauth_required`) would be signed out.\n */\nconst CREDENTIAL_PROCEDURES: readonly string[] = [\n \"auth/signIn\",\n \"auth/signUp\",\n \"auth/requestPasswordReset\",\n \"auth/confirmPasswordReset\",\n \"auth/confirmEmail\",\n \"auth/confirmEmailChange\",\n \"auth/verifyTotp\",\n \"auth/verifyBackupCode\",\n \"auth/requestEmailOtp\",\n \"auth/verifyEmailOtp\",\n];\n\n/**\n * The RPCLink routes per-procedure calls under a URL whose pathname ends\n * with the procedure path (joined as `/segment/segment`), so procedures are\n * detected by suffix. endsWith keeps trailing query strings or link-level\n * prefixes from breaking detection.\n */\nfunction requestPathname(request: Request): string {\n try {\n return new URL(request.url).pathname;\n } catch {\n return \"\";\n }\n}\n\n/** Detect whether a request targets the refresh procedure. */\nfunction isRefreshRequest(request: Request): boolean {\n if (request.headers.get(REFRESH_HEADER) === \"1\") return true;\n return requestPathname(request).endsWith(\"/auth/refresh\");\n}\n\n/** Detect whether a request targets one of the `CREDENTIAL_PROCEDURES`. */\nfunction isCredentialRequest(request: Request): boolean {\n const path = requestPathname(request);\n return CREDENTIAL_PROCEDURES.some((procedure) => path.endsWith(`/${procedure}`));\n}\n\nexport interface AttachRefreshInterceptorOptions {\n readonly transport: Transport;\n readonly auth: {\n readonly _refresh: () => Promise<Session>;\n readonly _signOutLocal: () => void;\n readonly getSession: () => Session | null;\n };\n}\n\n/**\n * Install a fetch interceptor that, on a 401 response, calls auth._refresh\n * once and retries the original request. Concurrent 401s de-duplicate\n * onto a single in-flight refresh promise. A 401/403 from refresh itself or\n * a 401 from the retry triggers a local sign-out and propagates the original\n * 401 to the caller (after which orpc throws ORPCError(\"UNAUTHORIZED\")). A\n * refresh that fails for any other reason (offline, 5xx) also propagates the\n * original 401, but keeps the session so a later call can succeed. A 401 from\n * a credential procedure (`CREDENTIAL_PROCEDURES`) is never refreshed or\n * replayed: it rejects the credentials, not the session.\n */\nexport function attachRefreshInterceptor(opts: AttachRefreshInterceptorOptions): void {\n let inflight: Promise<Session> | null = null;\n\n const refreshOnce = (): Promise<Session> => {\n if (!inflight) {\n inflight = opts.auth._refresh().finally(() => {\n inflight = null;\n });\n }\n return inflight;\n };\n\n opts.transport._setFetchInterceptor(async (request, next) => {\n // Refresh calls must never trigger a recursive refresh, and a 401 from a\n // credential procedure is a wrong password or code, not a dead session.\n if (isRefreshRequest(request) || isCredentialRequest(request)) {\n return next(request);\n }\n\n // Capture the request body now, before we pass `request` to `next` —\n // once fetch consumes a Request body it can no longer be cloned.\n const buffered = await request.clone().arrayBuffer();\n\n const firstResponse = await next(request);\n if (firstResponse.status !== 401) return firstResponse;\n\n try {\n await refreshOnce();\n } catch {\n // _refresh decides whether the session survives: it signs out only\n // when the server rejected the token, not on a network/server failure.\n return firstResponse;\n }\n\n // Rebuild headers so the new Authorization header replaces the stale\n // one — the RPCLink only re-evaluates its own `headers()` once per call,\n // so we patch the bearer ourselves here.\n const replayedHeaders = new Headers(request.headers);\n const session = opts.auth.getSession();\n if (session) {\n replayedHeaders.set(\"Authorization\", `Bearer ${session.accessToken}`);\n } else {\n replayedHeaders.delete(\"Authorization\");\n }\n const replayed = new Request(request.url, {\n method: request.method,\n headers: replayedHeaders,\n body: buffered.byteLength > 0 ? buffered : undefined,\n credentials: request.credentials,\n mode: request.mode,\n cache: request.cache,\n redirect: request.redirect,\n referrer: request.referrer,\n integrity: request.integrity,\n });\n\n const retried = await next(replayed);\n if (retried.status === 401) {\n // Refresh succeeded server-side but the server still rejects — the\n // session is dead. Sign out locally and let orpc surface the 401.\n opts.auth._signOutLocal();\n }\n return retried;\n });\n}\n","// Resolving a studio's join code (#529): the one call an app makes before\n// it has a client id. The code on a studio's Fytkit Coach QR code or join\n// link names the studio; this turns it into the client id to pass to\n// `createFytkitClient`, with what a welcome screen needs.\n//\n// A plain `GET /api/v1/join/{code}`: it takes no credentials and needs no\n// client, so it is a function of its own rather than a client method.\n\nimport { createORPCErrorFromJson, isORPCErrorJson } from \"@orpc/client\";\nimport { DEFAULT_BASE_URL } from \"./base-url\";\nimport { normalizeError } from \"./error\";\nimport { isRecord } from \"./is-record\";\nimport { retryAfterHeaderMs } from \"./retry-after\";\nimport type { FytkitError } from \"./types\";\n\n/** Options for {@link resolveJoinCode}. */\nexport interface ResolveJoinCodeOptions {\n /** The API's origin, as for `createFytkitClient`. Defaults to `https://api.fytkit.com`. */\n readonly baseUrl?: string;\n /** A `fetch` to use instead of the global one. */\n readonly fetch?: typeof fetch;\n}\n\n/**\n * The design the app paints for the studio: its Fytkit Coach theme (an\n * app-only one if the studio picked one, otherwise its published site design),\n * the theme's palette and the shades derived from it. Every colour is a\n * `#rrggbb` hex string.\n */\nexport interface JoinCodeTheme {\n /** The site template the design comes from (the app calls it the theme). */\n readonly templateId: string;\n /** The template's colour theme (the app calls it the palette). */\n readonly themeId: string;\n /** The theme's curated colours. */\n readonly palette: {\n readonly primary: string;\n readonly secondary: string;\n readonly accent: string;\n readonly background: string;\n readonly text: string;\n };\n /** Shades derived from the palette, for states and text on fills. */\n readonly variants: {\n /** Hover/press shade of `primary`. */\n readonly primaryDeep: string;\n /** Tinted background of `primary`. */\n readonly primarySoft: string;\n /** Text that reads on `primary`. */\n readonly onPrimary: string;\n /** Hover/press shade of `accent`. */\n readonly accentDeep: string;\n /** Tinted background of `accent`. */\n readonly accentSoft: string;\n /** Text that reads on `accent`. */\n readonly onAccent: string;\n /** The highlight fill, legible under dark ink text. */\n readonly highlight: string;\n /** Hover/press shade of `highlight`. */\n readonly highlightDeep: string;\n /** Tinted background of `highlight`. */\n readonly highlightSoft: string;\n };\n}\n\n/** A coach a join code's studio names publicly. */\nexport interface JoinCodeCoach {\n /** The coach's public name. */\n readonly name: string;\n /** The coach's public profile image, a stable URL; null when there is none. */\n readonly avatarUrl: string | null;\n}\n\n/** What a join code resolves to. */\nexport interface JoinCodeResolution {\n /** The code in canonical form: upper case, no spaces or hyphens. */\n readonly code: string;\n /** The studio's Fytkit Coach client id: pass it to `createFytkitClient`. */\n readonly clientId: string;\n /** What the welcome screen names and shows. */\n readonly studio: {\n /** The name members know the studio by. */\n readonly name: string;\n /** The studio's logo, a stable public URL; null when it has none. */\n readonly logoUrl: string | null;\n /**\n * Who coaches there, as the studio's public site shows them: today one\n * entry, the studio's own coach profile (never staff). At most 3.\n */\n readonly coaches: readonly JoinCodeCoach[];\n /** Published programmes, any access level; drafts and archived ones never count. */\n readonly programCount: number;\n };\n /** The design the app paints for the studio. */\n readonly theme: JoinCodeTheme;\n}\n\n/**\n * What the server answers for an unknown or revoked code, and what this\n * answers itself, without a request, for input that can't safely go in the\n * URL path.\n */\nconst INACTIVE: FytkitError = {\n kind: \"not_found\",\n refusal: { reason: \"join_code_inactive\", data: {} },\n};\n\n/**\n * Whitespace, the hyphen-minus and the dashes phones substitute for it: the\n * separators the server ignores too (src/lib/join-code/code.ts in\n * fytkit-web, which a test there checks this against).\n */\nconst IGNORED_SEPARATORS = /[\\s\\-\\u2010-\\u2015\\u2212]/g;\n\n/**\n * Only what keeps the URL path safe: letters and digits, at most as many as\n * the server reads. The code's alphabet and length are the server's to\n * decide, so a format change never needs a new app build.\n */\nconst PATH_SAFE_CODE = /^[A-Za-z0-9]{1,64}$/;\n\n/** The code to put in the path, upper-cased; null when it isn't path-safe. */\nfunction pathSafeJoinCode(input: string): string | null {\n const candidate = input.replace(IGNORED_SEPARATORS, \"\");\n return PATH_SAFE_CODE.test(candidate) ? candidate.toUpperCase() : null;\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n}\n\n/** The refusal in a non-2xx response, as every SDK method reports one. */\nfunction refusalOf(response: Response, body: unknown): FytkitError {\n const wait = () => retryAfterHeaderMs(response.headers.get(\"retry-after\"), Date.now()) ?? 0;\n // A 404 that isn't the route's own oRPC answer comes from a wrong\n // `baseUrl` or a proxy, not from the code: it stays a plain server error\n // (below), so a member is never told their code is dead because of it.\n if (isORPCErrorJson(body)) {\n const error = normalizeError(createORPCErrorFromJson(body));\n // A 429 without the hint in its data (a proxy's): read the header instead.\n if (error.kind === \"rate_limited\" && error.retryAfter === 0) {\n return { ...error, retryAfter: wait() };\n }\n return error;\n }\n if (response.status === 429) return { kind: \"rate_limited\", retryAfter: wait() };\n return { kind: \"server\", status: response.status };\n}\n\n/**\n * Resolves a studio's join code, as its QR code or join link carries it or a\n * member types it, to the studio's Fytkit Coach client id, name, logo, app\n * theme, public coach profile and published programme count. Needs no client\n * id or session. Codes are case-insensitive and spaces and hyphens are\n * ignored, so pass what the member typed as it is.\n *\n * Rejects with a `FytkitError`, as every SDK method does:\n * - `{ kind: \"not_found\", refusal: { reason: \"join_code_inactive\" } }`: no\n * studio has that code, the studio replaced it with a new one, or the\n * input has something other than letters, digits, spaces and hyphens, or\n * more than 64 letters and digits (separators are stripped first; then\n * without a request). Show\n * \"This code is no longer active. Ask your coach for a new one.\" The\n * cases are deliberately indistinguishable.\n * - `{ kind: \"rate_limited\", retryAfter }`: 60 lookups a minute per IP\n * address; wait `retryAfter` milliseconds.\n * - `{ kind: \"network\" }` when the request never reached the server.\n * - `{ kind: \"server\", status }` for anything else, e.g. a 404 that isn't\n * the API's own answer (a wrong `baseUrl`, or a proxy in front of it).\n *\n * ```ts\n * const studio = await resolveJoinCode(scannedCode);\n * const fytkit = createFytkitClient({ clientId: studio.clientId });\n * ```\n */\nexport async function resolveJoinCode(\n code: string,\n options: ResolveJoinCodeOptions = {},\n): Promise<JoinCodeResolution> {\n // Anything that isn't letters and digits once separators are gone (`/`,\n // `.`, `..`, `?`, `%`) is refused here, so it never reaches the path.\n // Whether the rest is a code at all is the server's call.\n const canonical = pathSafeJoinCode(code);\n if (canonical === null) throw INACTIVE;\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const doFetch: typeof fetch = options.fetch ?? fetch.bind(globalThis);\n let response: Response;\n try {\n response = await doFetch(`${baseUrl}/api/v1/join/${canonical}`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n } catch (error) {\n throw normalizeError(error);\n }\n\n const body = await readJson(response);\n if (response.ok) {\n if (isRecord(body) && isRecord(body.data)) {\n // The server's documented shape, trusted as every other response is.\n return body.data as unknown as JoinCodeResolution;\n }\n throw { kind: \"server\", status: response.status } satisfies FytkitError;\n }\n throw refusalOf(response, body);\n}\n","// \"Subscribe\" or \"What your plan includes\" (#538). Fytkit Coach takes no\n// in-app purchase: where the member's store allows it, the app links out to\n// the studio's Stripe checkout; everywhere else it shows what the plan\n// includes. `app.bootstrap` carries the list of storefronts where linking out\n// is allowed (`data.paymentPolicy`); this reads it for the storefront the\n// device's store reports. It fails closed: an unknown storefront, a missing\n// code or an unmet condition is plan info.\n\nimport type { AppBootstrapData } from \"./app\";\n\n/** `app.bootstrap`'s `data.paymentPolicy`. */\nexport type PaymentPolicy = AppBootstrapData[\"paymentPolicy\"];\n\n/** The store the running app was installed from. */\nexport type PaymentPlatform = PaymentPolicy[\"linkOut\"][number][\"platform\"];\n\n/** Options for {@link resolvePaymentMode}. */\nexport interface ResolvePaymentModeOptions {\n /** The store the running app was installed from: `\"ios\"` or `\"android\"`. */\n readonly platform: PaymentPlatform;\n /**\n * The storefront the store reports, case-insensitive: on iOS StoreKit's\n * `Storefront.current?.countryCode` (ISO 3166-1 alpha-3, e.g. \"USA\"); on\n * Android Play Billing's `BillingConfig.getCountryCode()` (alpha-2, e.g.\n * \"US\"). Pass `null` when the store reports none: that is plan info.\n */\n readonly storefront: string | null | undefined;\n /**\n * The conditions the app meets in this storefront (see `conditions` in\n * `paymentPolicy.linkOut`), e.g. `\"apple_external_purchase_link_entitlement\"`\n * once `ExternalPurchaseLink.canOpen` is true. A storefront whose\n * conditions aren't all met, or names one the app doesn't know, is plan info.\n */\n readonly conditionsMet?: readonly string[];\n}\n\n/**\n * What to show:\n * - `link_out`: \"Subscribe\", opening the studio's checkout outside the app,\n * in the way each of `conditions` requires (none: a plain link to the\n * system browser);\n * - `plan_info`: \"What your plan includes\", with no purchase and no link to one.\n */\nexport type PaymentMode =\n | { readonly mode: \"link_out\"; readonly conditions: readonly string[] }\n | { readonly mode: \"plan_info\" };\n\nconst PLAN_INFO: PaymentMode = { mode: \"plan_info\" };\n\n/**\n * Decides, for the storefront the device's store reports, whether the app\n * shows \"Subscribe\" (a link out) or \"What your plan includes\". Pure: it reads\n * the policy from a stored `app.bootstrap`, so it works offline. A missing or\n * malformed policy (e.g. a snapshot stored before it existed) is plan info.\n *\n * ```ts\n * const { mode } = resolvePaymentMode(data.paymentPolicy, {\n * platform: \"ios\",\n * storefront: storefrontCountryCode, // from StoreKit, e.g. \"USA\"\n * });\n * if (mode === \"link_out\") showSubscribe();\n * else showWhatYourPlanIncludes();\n * ```\n */\nexport function resolvePaymentMode(\n policy: PaymentPolicy | null | undefined,\n options: ResolvePaymentModeOptions,\n): PaymentMode {\n // A snapshot stored before the policy existed, or a malformed one, has no\n // list to read: plan info, as for a storefront it doesn't name.\n if (!policy || !Array.isArray(policy.linkOut)) return PLAN_INFO;\n const storefront = options.storefront?.trim().toUpperCase() ?? \"\";\n if (storefront === \"\") return PLAN_INFO;\n const rule = policy.linkOut.find(\n (entry) =>\n entry?.platform === options.platform &&\n typeof entry.storefront === \"string\" &&\n entry.storefront.toUpperCase() === storefront,\n );\n if (!rule || !Array.isArray(rule.conditions)) return PLAN_INFO;\n const met = new Set(options.conditionsMet ?? []);\n if (!rule.conditions.every((condition) => met.has(condition))) return PLAN_INFO;\n return { mode: \"link_out\", conditions: rule.conditions };\n}\n","// Live updates for a trainee's conversation (SDK1, #461): the third-party\n// counterpart of the member portal's own channel. `messages.realtimeToken`\n// hands out a 15-minute token and the one private Supabase Realtime topic it\n// may join (`member:<client id>`); `app.bootstrap` names the project's public\n// URL and anon key. The database broadcasts `thread_changed` on that topic\n// whenever the conversation changes, with no content: a cue to read\n// `messages.list` and `messages.unreadCount` again, never the data itself.\n//\n// The SDK has no runtime dependency on supabase-js: the app passes a factory\n// that creates a Supabase client, and this module only uses the few members\n// described by `RealtimeClientLike`. Self-contained on purpose (no imports),\n// so the shapes can be checked against supabase-js's own types. The token is\n// set on the socket by hand (`realtime.setAuth(token)`), which supabase-js\n// keeps only from 2.87.1: earlier 2.x clients put the anon key back on the\n// next heartbeat, and the private channel is lost.\n\n/** fytkit's public Supabase Realtime settings, from `app.bootstrap` (`data.realtime`). */\nexport interface RealtimeConnection {\n /** The Supabase project URL. */\n readonly url: string;\n /** The project's public anon key. Safe to ship in an app. */\n readonly anonKey: string;\n}\n\n/** `messages.realtimeToken`'s `data`: a token and the private topic it admits. */\nexport interface MessagesRealtimeGrant {\n /** A Supabase Realtime token for the signed-in trainee, valid until `expiresAt`. */\n readonly token: string;\n /** When the token expires (ISO 8601), 15 minutes after it was issued. */\n readonly expiresAt: string;\n /** The only private topic the token may join: `member:<client id>`. */\n readonly topic: string;\n}\n\n/** An auth-state event, as `auth.onAuthStateChange` reports it; only `type` is read. */\nexport interface MessagesRealtimeAuthEvent {\n /** `signed_in`, `signed_out`, `session_refreshed` or `signed_out_due_to_token_reuse`. */\n readonly type: string;\n}\n\n/** The calls the subscription needs. A `FytkitClient` is one. */\nexport interface MessagesRealtimeSource {\n /**\n * `app.bootstrap`, for the Realtime connection settings. Called without an\n * ETag, so it always answers `ok` with the data.\n */\n readonly app: {\n bootstrap(): PromiseLike<\n | { readonly status: \"ok\"; readonly data: { readonly realtime: RealtimeConnection } }\n | { readonly status: \"unchanged\" }\n >;\n };\n /** `messages.realtimeToken`, for the token and its topic. */\n readonly messages: {\n realtimeToken(\n input: Record<string, never>,\n ): PromiseLike<{ readonly data: MessagesRealtimeGrant }>;\n };\n /**\n * `auth.onAuthStateChange`, optional. When present, a sign-out on the\n * client (`signed_out`, `signed_out_due_to_token_reuse`) ends the\n * subscription at once with `refused`, rather than at its next renewal:\n * the trainee's channel never outlives their session on this client.\n */\n readonly auth?: {\n onAuthStateChange(listener: (event: MessagesRealtimeAuthEvent) => void): () => void;\n };\n}\n\n/**\n * The part of a supabase-js `RealtimeChannel` the subscription uses. A\n * channel that reports `CLOSED` is finished: supabase-js removes it from its\n * socket and never joins it again.\n */\nexport interface RealtimeChannelLike {\n /** Listens for one Broadcast event on the channel. */\n on(\n type: \"broadcast\",\n filter: { readonly event: string },\n callback: (message: unknown) => void,\n ): RealtimeChannelLike;\n /** Joins the channel; `status` is `SUBSCRIBED`, `CHANNEL_ERROR`, `TIMED_OUT` or `CLOSED`. */\n subscribe(callback: (status: string, error?: Error) => void): RealtimeChannelLike;\n}\n\n/**\n * The part of a supabase-js `SupabaseClient` the subscription uses.\n * `@supabase/supabase-js` 2.87.1 or later: earlier clients swap the token the\n * subscription sets for the anon key on the next heartbeat.\n */\nexport interface RealtimeClientLike {\n /** Creates a channel on `name`; the subscription always asks for a private one. */\n channel(\n name: string,\n options: { readonly config: { readonly private: true } },\n ): RealtimeChannelLike;\n /** Leaves and forgets a channel. */\n removeChannel(channel: RealtimeChannelLike): unknown;\n /** The client's Realtime socket: `setAuth(token)` hands it the token. */\n readonly realtime: {\n setAuth(token: string): Promise<void>;\n disconnect(): void;\n };\n}\n\n/**\n * Creates a new Supabase client for the subscription, with\n * `@supabase/supabase-js` 2.87.1 or later:\n *\n * ```ts\n * ({ url, anonKey }) =>\n * createClient(url, anonKey, {\n * auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n * })\n * ```\n *\n * The client carries the trainee's Realtime token and nothing else, so its\n * auth stays off: no stored session, no refresh, and no sign-in read from\n * your page's URL that could replace the token. Return a new client on every\n * call: the subscription sets its own token on the client's socket and\n * disconnects it when it ends, so a client the app uses for anything else\n * would lose both. A factory that throws ends the subscription with\n * `fallback`.\n */\nexport type RealtimeClientFactory = (connection: RealtimeConnection) => RealtimeClientLike;\n\n/**\n * How the conversation is kept current:\n * - `connecting`: before the first answer either way, and while it moves to\n * another trainee's channel. Poll meanwhile.\n * - `live`: the channel is joined; changes arrive as `onChange`. Stop polling.\n * - `fallback`: Realtime is unavailable. Poll at\n * {@link MESSAGES_FALLBACK_POLL_MS}. For now, and `live` may follow, when\n * the token or the connection settings couldn't be fetched (retried every\n * minute), or the channel errored or timed out (supabase-js joins it\n * again by itself), or Realtime closed it (a Realtime restart, a token\n * that expired before its renewal landed: the subscription joins a fresh\n * channel after 5 seconds, then 15, then every minute, with a new token\n * first if the one it holds has expired). For good, and polling carries\n * on, when the server has no live updates to offer (a token without\n * `topic`, no connection settings, or `app.bootstrap` refused) or the\n * factory threw.\n * - `refused`: the token request was refused (`auth_required`,\n * `forbidden`, `not_found` or `validation`), asking again won't change\n * that, and nearly always because there is no trainee to listen for\n * (signed out, account closed, a trainer previewing the portal); or the\n * `FytkitClient` passed in signed out. Final: the subscription has\n * stopped and asks nothing more; the conversation reads are refused the\n * same way.\n */\nexport type MessagesRealtimeStatus = \"connecting\" | \"live\" | \"fallback\" | \"refused\";\n\n/** What {@link subscribeToMessages} needs besides the client. */\nexport interface SubscribeToMessagesOptions {\n /** Creates the Supabase client the subscription owns. See {@link RealtimeClientFactory}. */\n readonly realtime: RealtimeClientFactory;\n /**\n * The conversation may have changed: read `messages.list` and\n * `messages.unreadCount` again. Also called on every (re)join, since\n * changes while the channel was down were not heard.\n */\n readonly onChange: () => void;\n /** Each change of {@link MessagesRealtimeStatus}, starting with `connecting`. */\n readonly onStatus?: (status: MessagesRealtimeStatus) => void;\n}\n\n/**\n * How often to poll while the status is `connecting` or `fallback`: the open\n * conversation every 10 seconds, the unread count every 60. Polling spends\n * the app's shared per-minute request budget, so stop while `live`.\n */\nexport const MESSAGES_FALLBACK_POLL_MS = {\n conversation: 10_000,\n unreadCount: 60_000,\n} as const satisfies Readonly<Record<\"conversation\" | \"unreadCount\", number>>;\n\n/** The event the database broadcasts on a trainee's topic. */\nconst THREAD_CHANGED_EVENT = \"thread_changed\";\n/** The prefix of the only topic a trainee's token admits. */\nconst MEMBER_TOPIC_PREFIX = \"member:\";\n/** Ask for a new token this long before the current one expires. */\nconst TOKEN_RENEW_BEFORE_MS = 60_000;\n/** Never ask again sooner than this, however close the expiry. */\nconst TOKEN_MIN_DELAY_MS = 5_000;\n/** After a request that failed for now, try again this often. */\nconst RETRY_MS = 60_000;\n/**\n * The waits before each fresh join after Realtime closes the channel, from\n * the first closure on; the last repeats until a join subscribes. The same\n * as the member portal's own channel.\n */\nconst REJOIN_DELAYS_MS = [5_000, 15_000, 60_000] as const;\n\n/** Auth events after which there is no trainee on the client to listen for. */\nconst SIGNED_OUT_EVENTS: ReadonlySet<string> = new Set([\n \"signed_out\",\n \"signed_out_due_to_token_reuse\",\n]);\n\n/** Error kinds that asking again won't change: no trainee here, or a bad request. */\nconst FINAL_ERROR_KINDS: ReadonlySet<string> = new Set([\n \"auth_required\",\n \"forbidden\",\n \"not_found\",\n \"validation\",\n]);\n\nfunction isFinalError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null || !(\"kind\" in error)) return false;\n const kind: unknown = error.kind;\n return typeof kind === \"string\" && FINAL_ERROR_KINDS.has(kind);\n}\n\nfunction isMemberTopic(topic: unknown): topic is string {\n return (\n typeof topic === \"string\" &&\n topic.startsWith(MEMBER_TOPIC_PREFIX) &&\n topic.length > MEMBER_TOPIC_PREFIX.length\n );\n}\n\nfunction isConnection(value: unknown): value is RealtimeConnection {\n if (typeof value !== \"object\" || value === null) return false;\n if (!(\"url\" in value) || !(\"anonKey\" in value)) return false;\n return (\n typeof value.url === \"string\" &&\n value.url.length > 0 &&\n typeof value.anonKey === \"string\" &&\n value.anonKey.length > 0\n );\n}\n\nfunction renewalDelay(expiresAt: string, now: number): number {\n const expiry = Date.parse(expiresAt);\n if (Number.isNaN(expiry)) return RETRY_MS;\n return Math.max(TOKEN_MIN_DELAY_MS, expiry - TOKEN_RENEW_BEFORE_MS - now);\n}\n\n/** Whether a token still has long enough to run for a join to be worth sending with it. */\nfunction isUsable(expiresAt: string, now: number): boolean {\n return Date.parse(expiresAt) - now > TOKEN_MIN_DELAY_MS;\n}\n\n/** The wait before the fresh join after `closures` closures with no subscription between. */\nfunction rejoinDelay(closures: number): number {\n const last = REJOIN_DELAYS_MS.length - 1;\n return REJOIN_DELAYS_MS[Math.min(Math.max(closures - 1, 0), last)];\n}\n\nfunction ignore(): void {\n // A promise whose outcome nothing waits for.\n}\n\n/**\n * Hands the socket a token. Settles once the socket has it, or once it\n * failed, and never throws: the join follows either way, and a channel\n * without the token reports its own failure.\n */\nfunction authorise(client: RealtimeClientLike, token: string): Promise<void> {\n return new Promise<void>((resolve) => {\n resolve(client.realtime.setAuth(token));\n }).then(ignore, ignore);\n}\n\n/**\n * Subscribes to changes in the signed-in trainee's conversation over\n * Supabase Realtime, and returns a function that ends the subscription.\n *\n * It fetches a token (`messages.realtimeToken`) and the connection settings\n * (`app.bootstrap`), creates a client with `options.realtime`, hands the\n * socket the token, and joins the private topic the token names. The token\n * is renewed a minute before it expires, on the open channel; a renewed\n * token for another topic (another trainee signed in on the same client)\n * leaves that channel and its client and joins the new topic on a new one.\n * A channel Realtime closes is joined afresh on the same client after a\n * wait (5 seconds, 15, then every minute), with the token handed to the\n * socket again first, or a new one if it has expired. Requests that fail\n * for now are retried every minute; a refused token (`auth_required`,\n * `forbidden`, `not_found`, `validation`) ends the subscription with\n * `refused`.\n *\n * `onChange` carries no data: read the conversation and the unread count\n * again. Poll while the status is `connecting` or `fallback`. Needs\n * `@supabase/supabase-js` 2.87.1 or later (see {@link RealtimeClientFactory}).\n *\n * @example\n * ```ts\n * import { createClient } from \"@supabase/supabase-js\";\n * import { MESSAGES_FALLBACK_POLL_MS, subscribeToMessages } from \"@fytkit/sdk\";\n *\n * const unsubscribe = subscribeToMessages(fytkit, {\n * // A client for the subscription alone, carrying the trainee's token only.\n * realtime: ({ url, anonKey }) =>\n * createClient(url, anonKey, {\n * auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n * }),\n * onChange: () => refreshConversation(),\n * onStatus: (status) => setPolling(status === \"connecting\" || status === \"fallback\"),\n * });\n * ```\n */\nexport function subscribeToMessages(\n client: MessagesRealtimeSource,\n options: SubscribeToMessagesOptions,\n): () => void {\n let closed = false;\n let lastStatus: MessagesRealtimeStatus | null = null;\n // The renewal timer: the next token request, or a retry of one.\n let timer: ReturnType<typeof setTimeout> | null = null;\n let renewing = false;\n let connection: RealtimeConnection | null = null;\n // The client the subscription owns, the grant whose token its socket\n // holds, and the channel once joined: one trainee's, replaced together.\n let socket: RealtimeClientLike | null = null;\n let held: MessagesRealtimeGrant | null = null;\n let channel: RealtimeChannelLike | null = null;\n // A fresh join waiting after Realtime closed the channel, and how many\n // closures came one after another without a subscription between.\n let rejoinTimer: ReturnType<typeof setTimeout> | null = null;\n let closures = 0;\n // Stops listening for a sign-out on the client, once the subscription ends.\n let stopAuthListener: (() => void) | null = null;\n\n const report = (status: MessagesRealtimeStatus): void => {\n if (closed || status === lastStatus) return;\n lastStatus = status;\n options.onStatus?.(status);\n };\n\n const clearTimer = (): void => {\n if (timer === null) return;\n clearTimeout(timer);\n timer = null;\n };\n\n const after = (delayMs: number, run: () => Promise<void>): void => {\n clearTimer();\n if (closed) return;\n timer = setTimeout(() => {\n timer = null;\n void run();\n }, delayMs);\n };\n\n const cancelRejoin = (): void => {\n if (rejoinTimer !== null) clearTimeout(rejoinTimer);\n rejoinTimer = null;\n };\n\n /** Leaves the channel and drops the socket; nothing they report counts any more. */\n const drop = (): void => {\n const open = socket;\n const joined = channel;\n socket = null;\n held = null;\n channel = null;\n cancelRejoin();\n closures = 0;\n if (open === null) return;\n if (joined !== null) Promise.resolve(open.removeChannel(joined)).then(ignore, ignore);\n open.realtime.disconnect();\n };\n\n const close = (): void => {\n if (closed) return;\n closed = true;\n clearTimer();\n drop();\n const stop = stopAuthListener;\n stopAuthListener = null;\n stop?.();\n };\n\n /**\n * Stops, then reports a last status: nothing is asked or reported again.\n * Stopping first means an `onStatus` that throws can't keep the channel.\n */\n const finish = (status: MessagesRealtimeStatus): void => {\n if (closed) return;\n const changed = status !== lastStatus;\n lastStatus = status;\n close();\n if (changed) options.onStatus?.(status);\n };\n\n const join = (joinTopic: string, open: RealtimeClientLike): void => {\n // Unsubscribed, the socket was replaced while it took the token, or a\n // join on it has been made since.\n if (closed || socket !== open || channel !== null) return;\n const joined = open.channel(joinTopic, { config: { private: true } });\n channel = joined;\n // A channel the subscription has left (a new trainee's replaced it,\n // Realtime closed it, or it ended) reports nothing.\n const current = (): boolean => !closed && channel === joined;\n joined\n .on(\"broadcast\", { event: THREAD_CHANGED_EVENT }, () => {\n if (current()) options.onChange();\n })\n .subscribe((status) => {\n if (!current()) return;\n if (status === \"SUBSCRIBED\") {\n closures = 0;\n report(\"live\");\n // Changes between the last read and the join were not heard.\n options.onChange();\n return;\n }\n report(\"fallback\");\n // CHANNEL_ERROR or TIMED_OUT: realtime-js joins this channel again\n // by itself. CLOSED (a Realtime restart, a token that expired before\n // its renewal landed): realtime-js has let it go for good, so join a\n // fresh one after a wait.\n if (status === \"CLOSED\") {\n channel = null;\n scheduleRejoin();\n }\n });\n };\n\n /**\n * Hands `open` the grant's token, then joins its topic: a join sent\n * before the socket holds the token carries the anon key alone, which the\n * private topic refuses.\n */\n const authoriseAndJoin = (grant: MessagesRealtimeGrant, open: RealtimeClientLike): void => {\n void authorise(open, grant.token).then(() => join(grant.topic, open));\n };\n\n /** Joins afresh after Realtime closed the channel, with a token it will take. */\n const rejoin = (): void => {\n const open = socket;\n const grant = held;\n if (closed || open === null || grant === null || channel !== null) return;\n // An expired token would be refused: the renewal joins with a new one.\n if (!isUsable(grant.expiresAt, Date.now())) {\n void renew();\n return;\n }\n authoriseAndJoin(grant, open);\n };\n\n const scheduleRejoin = (): void => {\n if (closed || rejoinTimer !== null) return;\n closures += 1;\n rejoinTimer = setTimeout(() => {\n rejoinTimer = null;\n rejoin();\n }, rejoinDelay(closures));\n };\n\n /** Opens a client on the settings and joins the grant's topic once its socket holds the token. */\n const connect = (grant: MessagesRealtimeGrant, settings: RealtimeConnection): void => {\n let opened: RealtimeClientLike;\n try {\n opened = options.realtime(settings);\n } catch {\n // The app's factory can't make a client (a malformed URL, say):\n // asking again won't change that.\n finish(\"fallback\");\n return;\n }\n socket = opened;\n held = grant;\n after(renewalDelay(grant.expiresAt, Date.now()), renew);\n authoriseAndJoin(grant, opened);\n };\n\n /** Opens the subscription on a grant: its topic, the settings (fetched once), a client. */\n const openGrant = async (grant: MessagesRealtimeGrant): Promise<void> => {\n // A server from before `topic` was added: no topic to join.\n if (!isMemberTopic(grant.topic)) {\n finish(\"fallback\");\n return;\n }\n\n if (connection === null) {\n let realtime: unknown;\n try {\n const bootstrap = await client.app.bootstrap();\n realtime = bootstrap.status === \"ok\" ? bootstrap.data.realtime : undefined;\n } catch (error) {\n if (closed) return;\n if (isFinalError(error)) {\n finish(\"fallback\");\n return;\n }\n report(\"fallback\");\n after(RETRY_MS, start);\n return;\n }\n if (closed) return;\n // A server from before the settings were published.\n if (!isConnection(realtime)) {\n finish(\"fallback\");\n return;\n }\n connection = realtime;\n }\n\n connect(grant, connection);\n };\n\n const renew = async (): Promise<void> => {\n // One request at a time: the one in flight joins when it lands.\n if (renewing) return;\n renewing = true;\n let grant: MessagesRealtimeGrant;\n try {\n grant = (await client.messages.realtimeToken({})).data;\n } catch (error) {\n if (closed) return;\n // A refusal stands: the token from before is not used again.\n if (isFinalError(error)) finish(\"refused\");\n else after(RETRY_MS, renew);\n return;\n } finally {\n renewing = false;\n }\n const current = socket;\n if (closed || current === null || held === null) return;\n if (grant.topic !== held.topic) {\n // Another trainee signed in on the same client: the open channel is\n // the previous trainee's. Leave it and open the new trainee's, as the\n // member portal does when its member changes.\n drop();\n report(\"connecting\");\n await openGrant(grant);\n return;\n }\n held = grant;\n after(renewalDelay(grant.expiresAt, Date.now()), renew);\n if (channel !== null) {\n // The open channel carries on with the new token; no rejoin.\n void authorise(current, grant.token);\n return;\n }\n // Realtime closed the channel: join afresh now, with the new token.\n cancelRejoin();\n authoriseAndJoin(grant, current);\n };\n\n const start = async (): Promise<void> => {\n let grant: MessagesRealtimeGrant;\n try {\n grant = (await client.messages.realtimeToken({})).data;\n } catch (error) {\n if (closed) return;\n if (isFinalError(error)) {\n finish(\"refused\");\n return;\n }\n report(\"fallback\");\n after(RETRY_MS, start);\n return;\n }\n if (closed) return;\n await openGrant(grant);\n };\n\n report(\"connecting\");\n // A sign-out leaves no trainee to listen for: stop now, not at the next\n // renewal, so the previous trainee's channel stays open no longer.\n stopAuthListener =\n client.auth?.onAuthStateChange((event) => {\n if (SIGNED_OUT_EVENTS.has(event.type)) finish(\"refused\");\n }) ?? null;\n void start();\n return close;\n}\n","import { createAppNamespace } from \"./app\";\nimport { createAuthNamespace } from \"./auth\";\nimport { DEFAULT_BASE_URL } from \"./base-url\";\nimport { createDataNamespaces } from \"./data\";\nimport { createLocalStorageAdapter, createMemoryStorageAdapter } from \"./storage\";\nimport { attachRefreshInterceptor, createTransport } from \"./transport\";\nimport type { FytkitClient, FytkitClientOptions } from \"./types\";\n\nexport function createFytkitClient(options: FytkitClientOptions): FytkitClient {\n if (!options || typeof options.clientId !== \"string\" || options.clientId.length === 0) {\n throw new Error(\"createFytkitClient: `clientId` is required\");\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const storage = options.storage ?? createLocalStorageAdapter(options.clientId);\n\n // `auth` is declared after `transport` because `createAuthNamespace`\n // needs the transport to make its RPC calls. The transport in turn needs\n // a `getSession` that reads the auth cache — we resolve the cycle with a\n // late-bound reference: the closure below reads `auth` lazily, which is\n // safe because no request can fire before construction finishes.\n let auth: ReturnType<typeof createAuthNamespace> | null = null;\n\n const transport = createTransport({\n baseUrl,\n clientId: options.clientId,\n getSession: () => auth?.getSession() ?? null,\n fetch: options.fetch,\n });\n\n const authInstance = createAuthNamespace({ transport, storage });\n auth = authInstance;\n attachRefreshInterceptor({ transport, auth: authInstance });\n\n const data = createDataNamespaces(transport);\n\n return {\n clientId: options.clientId,\n auth: {\n signUp: authInstance.signUp,\n signIn: authInstance.signIn,\n signOut: authInstance.signOut,\n getSession: authInstance.getSession,\n onAuthStateChange: authInstance.onAuthStateChange,\n requestPasswordReset: authInstance.requestPasswordReset,\n confirmPasswordReset: authInstance.confirmPasswordReset,\n confirmEmail: authInstance.confirmEmail,\n resendVerification: authInstance.resendVerification,\n verifyTotp: authInstance.verifyTotp,\n verifyBackupCode: authInstance.verifyBackupCode,\n requestEmailOtp: authInstance.requestEmailOtp,\n verifyEmailOtp: authInstance.verifyEmailOtp,\n getSecurityStatus: authInstance.getSecurityStatus,\n enableTotp: authInstance.enableTotp,\n verifyTotpSetup: authInstance.verifyTotpSetup,\n disableTwoFactor: authInstance.disableTwoFactor,\n regenerateBackupCodes: authInstance.regenerateBackupCodes,\n changePassword: authInstance.changePassword,\n changeEmail: authInstance.changeEmail,\n confirmEmailChange: authInstance.confirmEmailChange,\n listSessions: authInstance.listSessions,\n revokeSession: authInstance.revokeSession,\n revokeOtherSessions: authInstance.revokeOtherSessions,\n closeAccount: authInstance.closeAccount,\n },\n ...data,\n app: createAppNamespace(transport),\n };\n}\n\n// Public re-exports.\nexport { createLocalStorageAdapter, createMemoryStorageAdapter };\n// `PublicApi` is the inferred router type that backs every namespace\n// on `FytkitClient` (e.g. `client.workouts` is `PublicApi[\"workout\"]`).\n// Re-exported so consumers can write generics over the router shape —\n// previously this surfaced as an `ae-forgotten-export` warning on\n// `FytkitClient` in the api-extractor report. The api-extractor will\n// still warn about `PublicRouter`/`publicRouter` being unexported —\n// that is intentional, those are implementation-detail names from the\n// generated snapshot that should not be part of the consumer surface.\nexport type { PublicApi } from \"@fytkit/types\";\nexport type {\n AppBootstrapData,\n AppBootstrapOptions,\n AppBootstrapResult,\n AppNamespace,\n} from \"./app\";\nexport type {\n JoinCodeCoach,\n JoinCodeResolution,\n JoinCodeTheme,\n ResolveJoinCodeOptions,\n} from \"./join\";\nexport { resolveJoinCode } from \"./join\";\nexport type {\n PaymentMode,\n PaymentPlatform,\n PaymentPolicy,\n ResolvePaymentModeOptions,\n} from \"./payment-policy\";\nexport { resolvePaymentMode } from \"./payment-policy\";\nexport type {\n MessagesRealtimeAuthEvent,\n MessagesRealtimeGrant,\n MessagesRealtimeSource,\n MessagesRealtimeStatus,\n RealtimeChannelLike,\n RealtimeClientFactory,\n RealtimeClientLike,\n RealtimeConnection,\n SubscribeToMessagesOptions,\n} from \"./realtime\";\nexport { MESSAGES_FALLBACK_POLL_MS, subscribeToMessages } from \"./realtime\";\nexport type {\n FytkitRefusal,\n FytkitRefusalDataByReason,\n FytkitRefusalReason,\n FytkitRefusalRetryAt,\n} from \"./refusal\";\nexport { refusalData } from \"./refusal\";\nexport type {\n AuthState,\n FytkitClient,\n FytkitClientOptions,\n FytkitError,\n FytkitErrorCode,\n ResendVerificationResult,\n SecurityStatus,\n Session,\n SessionInfo,\n SignInResult,\n TokenStorage,\n TotpEnrollment,\n TwoFactorChallenge,\n TwoFactorMethod,\n TwoFactorToken,\n} from \"./types\";\n"]}
1
+ {"version":3,"sources":["../src/is-record.ts","../src/refusal.ts","../src/error.ts","../src/app.ts","../src/events.ts","../src/auth.ts","../src/base-url.ts","../src/data.ts","../src/storage.ts","../src/retry-after.ts","../src/transport.ts","../src/join.ts","../src/payment-policy.ts","../src/realtime.ts","../src/index.ts"],"names":["cached"],"mappings":"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","file":"index.js","sourcesContent":["// A guard shared by the SDK's readers of loosely typed server bodies\n// (`refusal.ts`, `retry-after.ts`). Internal: not exported from the package.\n\n/** A plain object (not null, not an array) whose keys can be read. */\nexport function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { isRecord } from \"./is-record\";\n\n/**\n * Machine-readable reasons the server attaches to a refusal, as\n * `refusal.reason` on a `FytkitError`. Branch on these rather than on message\n * text. The union is open: the server may add reasons without a breaking SDK\n * release, so always keep a fallback branch.\n *\n * Check-ins (`checkIns.*`):\n * - `\"entitlement_required\"` (`forbidden`, 403): the member's plan lacks\n * `coaching_checkins`. On `checkIns.current` the data also says whether the\n * member is scheduled and has sent check-ins before (`member`).\n * - `\"already_submitted\"` (`conflict`, 409): this period already has a\n * check-in, `checkinId`; open or edit it instead.\n * - `\"reviewed\"` (`conflict`, 409): the coach reviewed it, so it is locked.\n * - `\"questions_changed\"` (`conflict`, 409): the coach changed the questions;\n * read `checkIns.current` again.\n * - `\"not_scheduled\"`, `\"not_open\"` (with `opensOn`), `\"not_current_period\"`\n * (with `currentPeriodStart`), `\"invalid_photos\"` (with `uploadIds`)\n * (`server`, 400).\n * - `\"invalid_answers\"` (`validation`, 400): `issues` by question.\n *\n * Messages (`messages.*`):\n * - `\"entitlement_required\"` (`forbidden`, 403): the member may not send now;\n * `eligibility` says whether they need an upgrade (`\"upgrade_required\"`) or\n * should wait for a coach to reply (`\"wait_for_coach\"`).\n * - `\"invalid_attachments\"` (`server`, 400): `uploadIds` that can't be sent.\n * - `\"per_minute_limit\"`, `\"daily_limit\"` (`rate_limited`, 429): with `limit`\n * and `retryAt`, when sending is allowed again.\n *\n * Photo uploads (`checkIns.requestPhotoUpload`,\n * `messages.requestAttachmentUpload`):\n * - `\"too_many_pending_uploads\"` (`rate_limited`, 429): with `limit` and\n * `retryAt`.\n *\n * Subscription checkout (`subscription.createCheckout`):\n * - `\"already_subscribed\"`, `\"payment_issue\"` (`conflict`, 409).\n * - `\"max_clients\"` (`forbidden`, 403): the studio is full and takes no new\n * paying members. The server names this one by its quota (`quotaCode`)\n * and sends nothing else with it; the SDK reports it as a reason like the\n * rest, with no data.\n *\n * Billing portal (`subscription.createPortal`):\n * - `\"no_billing_account\"` (`not_found`, 404): the member never started a\n * paid plan here; hide \"Manage billing\".\n * - `\"no_connected_account\"`, `\"portal_unavailable\"` (`server`, 400): the\n * studio's billing isn't set up; send the member to their coach.\n *\n * Habits (`habits.*`):\n * - `\"entitlement_required\"` (`forbidden`, 403): the member's plan lacks\n * `coaching_habits`. On `habits.today` the data also says whether their\n * coach gave them a habit that hasn't ended (`member.assigned`): show an\n * upgrade prompt when it is true.\n *\n * Weigh-ins and measurements (`bodyMetrics.update`, `bodyMetrics.delete`,\n * `bodyMeasurements.delete`):\n * - `\"staff_entered\"` (`conflict`, 409): their coach entered it; it is the\n * coach's to change or remove.\n * - `\"checkin_sourced\"` (`conflict`, 409, `bodyMetrics.update`): a check-in\n * recorded it; change it by editing the check-in.\n *\n * Progress photos (`progressPhotos.*`):\n * - `\"consent_required\"` (`conflict`, 409): progress photos are off.\n * - `\"photo_limit_reached\"` (`conflict`, 409): the timeline holds `limit`\n * photos.\n * - `\"invalid_uploads\"` (`server`, 400): `uploadIds` that can't be added.\n * - `\"poses_differ\"` (`server`, 400): compare two photos of the same pose.\n *\n * Health (`health.submitIntake`, `health.reportInjury`):\n * - `\"consent_required\"` (`conflict`, 409, `reportInjury`): no consent on\n * file at `currentVersion`; send the report again with consent.\n * - `\"consent_outdated\"` (`conflict`, 409): the consent wording changed to\n * `currentVersion`; show it again.\n * - `\"too_many_pending\"` (`conflict`, 409, `reportInjury`): `limit` reports\n * already wait for the coach.\n * - `\"questions_changed\"` (`conflict`, 409), `\"invalid_answers\"`\n * (`validation`, 400) on `submitIntake`, as on check-ins.\n *\n * Join codes (`resolveJoinCode`):\n * - `\"join_code_inactive\"` (`not_found`, 404): no studio has the code, or\n * the studio replaced it; ask the member to get a new one from their coach.\n */\nexport type FytkitRefusalReason =\n | \"entitlement_required\"\n | \"already_submitted\"\n | \"reviewed\"\n | \"questions_changed\"\n | \"not_scheduled\"\n | \"not_open\"\n | \"not_current_period\"\n | \"invalid_answers\"\n | \"invalid_photos\"\n | \"invalid_attachments\"\n | \"per_minute_limit\"\n | \"daily_limit\"\n | \"too_many_pending_uploads\"\n | \"already_subscribed\"\n | \"payment_issue\"\n | \"max_clients\"\n | \"no_billing_account\"\n | \"no_connected_account\"\n | \"portal_unavailable\"\n | \"staff_entered\"\n | \"checkin_sourced\"\n | \"consent_required\"\n | \"consent_outdated\"\n | \"too_many_pending\"\n | \"photo_limit_reached\"\n | \"invalid_uploads\"\n | \"poses_differ\"\n | \"join_code_inactive\"\n // Keeps the union open (and autocompletion for the known reasons).\n | (string & {});\n\n/**\n * The server's structured refusal on a `FytkitError`: why it said no and\n * what it sent with that. Present only when the server named a reason.\n */\nexport interface FytkitRefusal {\n /** The machine-readable reason (see `FytkitRefusalReason`). */\n readonly reason: FytkitRefusalReason;\n /** The rest of the error's data, without `reason`. */\n readonly data: Readonly<Record<string, unknown>>;\n}\n\n/** When a limited action is allowed again, as the 429 refusals send it. */\nexport interface FytkitRefusalRetryAt {\n /** The limit that was reached. */\n readonly limit: number;\n /** ISO 8601 instant when the limit lifts. */\n readonly retryAt: string;\n /** Milliseconds from the refusal until `retryAt`; also the error's `retryAfter`. */\n readonly retryAfterMs?: number;\n}\n\n/**\n * What each known reason's `refusal.data` holds, for `refusalData`. The\n * server documents these shapes; the SDK does not check them at runtime, as\n * it trusts every other response shape. An empty record is a refusal that\n * sends nothing beyond its reason.\n */\nexport interface FytkitRefusalDataByReason {\n /** A coaching feature the member's plan lacks. */\n readonly entitlement_required:\n | {\n readonly entitlement: \"coaching_checkins\";\n /** On `checkIns.current` only: whether to offer check-ins at all. */\n readonly member?: { readonly scheduled: boolean; readonly hasCheckins: boolean };\n }\n | {\n readonly entitlement: \"coaching_messages\";\n readonly eligibility: \"upgrade_required\" | \"wait_for_coach\";\n }\n | {\n readonly entitlement: \"coaching_habits\";\n /** On `habits.today` only: whether their coach gave them a habit that hasn't ended. */\n readonly member?: { readonly assigned: boolean };\n };\n /** The check-in already sent for this period. */\n readonly already_submitted: { readonly checkinId: string; readonly submittedAt: string };\n /** The check-in the coach reviewed, so it is locked. */\n readonly reviewed: { readonly checkinId: string };\n /** The coach changed the questions; read `checkIns.current` again. */\n readonly questions_changed: Readonly<Record<string, never>>;\n /** The member has no check-in schedule. */\n readonly not_scheduled: Readonly<Record<string, never>>;\n /** `opensOn` is a calendar date, `YYYY-MM-DD`. */\n readonly not_open: { readonly opensOn: string };\n /** `currentPeriodStart` is a calendar date, `YYYY-MM-DD`. */\n readonly not_current_period: { readonly currentPeriodStart: string };\n /** Answers that don't fit their questions, by question (null: the whole check-in). */\n readonly invalid_answers: {\n readonly issues: readonly { readonly questionId: string | null; readonly message: string }[];\n };\n /** Photos that can't be added to the check-in; upload them again. */\n readonly invalid_photos: { readonly uploadIds: readonly string[] };\n /** Photos that can't be sent with the message; upload them again. */\n readonly invalid_attachments: { readonly uploadIds: readonly string[] };\n /** 20 messages a minute. */\n readonly per_minute_limit: FytkitRefusalRetryAt;\n /** 300 messages a day, until midnight in the studio's time zone. */\n readonly daily_limit: FytkitRefusalRetryAt;\n /** 20 photos requested but not yet sent. */\n readonly too_many_pending_uploads: FytkitRefusalRetryAt;\n /** The member already has an active subscription. */\n readonly already_subscribed: Readonly<Record<string, never>>;\n /** The member's last payment failed and the old subscription is still live. */\n readonly payment_issue: Readonly<Record<string, never>>;\n /** The studio is full. Nothing beyond the reason: the refusal carries no figures. */\n readonly max_clients: Readonly<Record<string, never>>;\n /** The member has no billing account at this studio. */\n readonly no_billing_account: Readonly<Record<string, never>>;\n /** The studio has no connected payments account. */\n readonly no_connected_account: Readonly<Record<string, never>>;\n /** The studio hasn't set up its billing portal. */\n readonly portal_unavailable: Readonly<Record<string, never>>;\n /** Their coach entered this weigh-in or measurement. */\n readonly staff_entered: Readonly<Record<string, never>>;\n /** A check-in recorded this weigh-in; edit the check-in instead. */\n readonly checkin_sourced: Readonly<Record<string, never>>;\n /**\n * Health: no consent on file at `currentVersion` (`health.reportInjury`).\n * Progress photos: they are off, and nothing else is sent.\n */\n readonly consent_required: { readonly currentVersion?: string };\n /** The health-data consent wording changed; `currentVersion` is the current one. */\n readonly consent_outdated: { readonly currentVersion: string };\n /** `limit` injury reports already wait for the coach. */\n readonly too_many_pending: { readonly limit: number };\n /** The progress-photo timeline holds `limit` photos. */\n readonly photo_limit_reached: { readonly limit: number };\n /** Photo uploads that can't be added; upload them again. */\n readonly invalid_uploads: { readonly uploadIds: readonly string[] };\n /** Two photos of different poses can't be compared. */\n readonly poses_differ: Readonly<Record<string, never>>;\n /** No studio has the join code, or the studio replaced it. */\n readonly join_code_inactive: Readonly<Record<string, never>>;\n}\n\n/**\n * The data of `error`'s refusal when its reason is `reason`, typed for that\n * reason; null when `error` is not a `FytkitError`, carries no refusal, or\n * was refused for another reason.\n *\n * ```ts\n * catch (err) {\n * const sent = refusalData(err, \"already_submitted\");\n * if (sent) return openCheckIn(sent.checkinId);\n * }\n * ```\n */\nexport function refusalData<R extends keyof FytkitRefusalDataByReason>(\n error: unknown,\n reason: R,\n): FytkitRefusalDataByReason[R] | null {\n if (!isRecord(error) || typeof error.kind !== \"string\") return null;\n const refusal = error.refusal;\n if (!isRecord(refusal) || refusal.reason !== reason || !isRecord(refusal.data)) return null;\n // The server's documented shape for this reason (see FytkitRefusalDataByReason).\n return refusal.data as FytkitRefusalDataByReason[R];\n}\n\n/**\n * The member cap is the one refusal the server names by its quota\n * (`quotaCode`) rather than a `reason`: a studio that takes no new members\n * refuses a member's checkout with it. The refusal carries no figures, only\n * `quotaCode`, and the SDK passes the reason alone: should anything else ever\n * arrive beside it, it is dropped rather than handed to the app.\n */\nconst MEMBER_CAP_QUOTA = \"max_clients\";\n\n/**\n * The refusal in an error's `data`, as a spread-able object so a kind only\n * gains the key when the server named a reason (existing `toEqual` shapes\n * stay intact): the reason, and the rest of the data without it.\n */\nexport function pickRefusal(data: unknown): { readonly refusal?: FytkitRefusal } {\n if (!isRecord(data)) return {};\n const { reason, ...rest } = data;\n if (typeof reason === \"string\" && reason.length > 0) return { refusal: { reason, data: rest } };\n if (data.quotaCode === MEMBER_CAP_QUOTA)\n return { refusal: { reason: MEMBER_CAP_QUOTA, data: {} } };\n return {};\n}\n","import { ORPCError } from \"@orpc/client\";\nimport { pickRefusal } from \"./refusal\";\nimport type { FytkitError } from \"./types\";\n\ninterface MaybeIssuesData {\n issues?: readonly unknown[];\n}\ninterface MaybeRetryAfterData {\n retryAfterMs?: number;\n}\n\nfunction pickIssues(data: unknown): readonly unknown[] | null {\n if (!data || typeof data !== \"object\") return null;\n const issues = (data as MaybeIssuesData).issues;\n return Array.isArray(issues) ? issues : null;\n}\n\nfunction pickRetryAfter(data: unknown): number {\n if (!data || typeof data !== \"object\") return 0;\n const raw = (data as MaybeRetryAfterData).retryAfterMs;\n return typeof raw === \"number\" && raw >= 0 ? raw : 0;\n}\n\n/**\n * A non-empty string field of the error's `data`, when the server sent one:\n * `code` (the machine-readable reason, e.g. \"invalid_password\") or\n * `enrollmentId` (the existing enrolment on \"Already enrolled\"). Returned as\n * a spread-able object so kinds only gain the key when there is a value —\n * existing `toEqual` shapes stay intact.\n */\nfunction pickStringField<K extends \"code\" | \"enrollmentId\">(\n data: unknown,\n key: K,\n): { [P in K]?: string } {\n if (!data || typeof data !== \"object\") return {};\n const raw = (data as Partial<Record<K, unknown>>)[key];\n return typeof raw === \"string\" && raw.length > 0 ? ({ [key]: raw } as { [P in K]?: string }) : {};\n}\n\n/**\n * Map any error thrown by `@orpc/client` (or the underlying fetch) into the\n * SDK's `FytkitError` discriminated union. The SDK never re-throws raw orpc\n * or network errors at the public surface.\n *\n * A refusal the server named (`data.reason`, see `FytkitRefusal`) rides along\n * as `refusal` on the kinds that can carry one, beside the fields each kind\n * already had: `forbidden.reason` stays the message text.\n */\nexport function normalizeError(err: unknown): FytkitError {\n // Network failures from fetch surface as TypeError.\n if (err instanceof TypeError) return { kind: \"network\" };\n\n if (err instanceof ORPCError) {\n const code = err.code;\n const status = err.status;\n const data: unknown = err.data;\n const message = err.message;\n\n switch (code) {\n case \"UNAUTHORIZED\":\n // Carry the server's reason (e.g. \"Invalid credentials\" on a failed\n // sign-in) so the consumer can show it instead of a generic\n // 'session expired' copy. The synthetic no-session case in auth.ts\n // is built directly and stays message-less.\n return { kind: \"auth_required\", message };\n case \"FORBIDDEN\":\n return {\n kind: \"forbidden\",\n reason: message || \"Forbidden\",\n ...pickStringField(data, \"code\"),\n ...pickRefusal(data),\n };\n case \"PAYMENT_REQUIRED\":\n return { kind: \"payment_required\", message: message || \"Payment Required\" };\n case \"CONFLICT\":\n return {\n kind: \"conflict\",\n message: message || \"Conflict\",\n ...pickStringField(data, \"enrollmentId\"),\n ...pickRefusal(data),\n };\n case \"NOT_FOUND\":\n return { kind: \"not_found\", ...pickRefusal(data) };\n case \"TOO_MANY_REQUESTS\":\n return { kind: \"rate_limited\", retryAfter: pickRetryAfter(data), ...pickRefusal(data) };\n case \"BAD_REQUEST\":\n case \"UNPROCESSABLE_CONTENT\": {\n const issues = pickIssues(data);\n if (issues) return { kind: \"validation\", issues, ...pickRefusal(data) };\n // No structured issues — still carry the server's explanation (and\n // its code, e.g. \"invalid_password\") so it isn't dropped into a bare\n // status code.\n return {\n kind: \"server\",\n status,\n message,\n ...pickStringField(data, \"code\"),\n ...pickRefusal(data),\n };\n }\n default:\n return {\n kind: \"server\",\n status,\n message,\n ...pickStringField(data, \"code\"),\n ...pickRefusal(data),\n };\n }\n }\n\n return { kind: \"server\", status: 0 };\n}\n","// `app.bootstrap` (#535): Fytkit Coach's one launch read. Everything the app\n// paints and gates at launch, for the studio the client id names: its name\n// and logo, the app theme, feature overrides, the minimum app version, the\n// legal links, the Realtime settings and the payment policy.\n//\n// The payload is a pure function of the studio's settings and carries a\n// strong ETag. A call with the ETag the app stored last time is answered 304\n// with no body when nothing changed. The typed oRPC client resolves a 304's\n// empty body to `undefined` (its type says it never does), so this wraps the\n// raw procedure in a discriminated union instead of handing that through.\n\nimport type { PublicApi } from \"@fytkit/types\";\nimport { normalizeError } from \"./error\";\nimport type { Transport } from \"./transport\";\nimport type { FytkitError } from \"./types\";\n\n/**\n * Everything the app reads at launch for the studio (`app.bootstrap`'s\n * `data`): `studio` (display name and logo URL), `theme` (the effective\n * template and theme with its palette, derived variants and the oldest app\n * release that can paint it), `themeMinAppVersions`, `home.layout`,\n * `featureOverrides`, `minAppVersion`, `links` (terms, privacy, help),\n * `realtime` (the public Supabase Realtime settings) and `paymentPolicy` (the\n * storefronts where the app may link out to subscribe; read it with\n * `resolvePaymentMode`).\n */\nexport type AppBootstrapData = Awaited<ReturnType<PublicApi[\"app\"][\"bootstrap\"]>>[\"data\"];\n\n/** Options for `app.bootstrap`. */\nexport interface AppBootstrapOptions {\n /**\n * The `etag` of the last bootstrap the app kept. When the studio's\n * settings are unchanged the server answers 304 and the call resolves to\n * `{ status: \"unchanged\" }`, so keep using the data stored with it. Omit it\n * (or pass `null`) on a first launch, or when no data is stored.\n */\n readonly etag?: string | null;\n}\n\n/**\n * What `app.bootstrap` resolves to:\n * - `ok`: the current settings, and the `etag` to store beside them and send\n * next time;\n * - `unchanged`: the `etag` you sent is still current (a 304 with no body):\n * the data you stored with it is up to date.\n */\nexport type AppBootstrapResult =\n | { readonly status: \"ok\"; readonly data: AppBootstrapData; readonly etag: string }\n | { readonly status: \"unchanged\"; readonly etag: string };\n\n/** `client.app`: the app's launch read. */\nexport interface AppNamespace {\n /**\n * Reads the studio's app launch settings. Needs the client id alone; a\n * signed-in member's session changes nothing in the payload. Pass the\n * `etag` of the data you stored last time to revalidate it: an unchanged\n * studio answers `{ status: \"unchanged\" }` without a body.\n *\n * Rejects with a `FytkitError`, as every SDK method does (e.g.\n * `{ kind: \"not_found\" }` for a studio that no longer exists,\n * `{ kind: \"network\" }` offline): keep painting the stored data then.\n *\n * ```ts\n * const stored = await loadBootstrap(); // { data, etag } | null\n * const result = await fytkit.app.bootstrap({ etag: stored?.etag });\n * const data = result.status === \"ok\" ? result.data : stored.data;\n * if (result.status === \"ok\") await saveBootstrap(result);\n * ```\n */\n readonly bootstrap: (options?: AppBootstrapOptions) => Promise<AppBootstrapResult>;\n}\n\n/** A bodiless answer (a 304) to a call that sent no `If-None-Match`. */\nconst UNEXPECTED_NOT_MODIFIED: FytkitError = { kind: \"server\", status: 304 };\n\nexport function createAppNamespace(transport: Transport): AppNamespace {\n const bootstrap: AppNamespace[\"bootstrap\"] = async (options = {}) => {\n const etag = options.etag ?? null;\n // A 304 resolves to `undefined` at runtime, whatever the client's type says.\n let output: Awaited<ReturnType<PublicApi[\"app\"][\"bootstrap\"]>> | undefined;\n try {\n output = await transport.client.app.bootstrap(undefined, {\n context: etag === null ? {} : { ifNoneMatch: etag },\n });\n } catch (error) {\n throw normalizeError(error);\n }\n if (output === undefined || output === null) {\n if (etag === null) throw UNEXPECTED_NOT_MODIFIED;\n return { status: \"unchanged\", etag };\n }\n return { status: \"ok\", data: output.data, etag: output.meta.etag };\n };\n return { bootstrap };\n}\n","export interface EventBus<T> {\n readonly on: (listener: (value: T) => void) => () => void;\n readonly emit: (value: T) => void;\n}\n\nexport function createEventBus<T>(): EventBus<T> {\n const listeners = new Set<(value: T) => void>();\n return {\n on: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n emit: (value) => {\n for (const listener of listeners) {\n try {\n listener(value);\n } catch (err) {\n // A buggy listener must not break siblings or the calling flow.\n console.error(\"[fytkit-sdk] event listener threw\", err);\n }\n }\n },\n };\n}\n","import { normalizeError } from \"./error\";\nimport { createEventBus } from \"./events\";\nimport type { Transport } from \"./transport\";\nimport type {\n AuthState,\n FytkitError,\n ResendVerificationResult,\n SecurityStatus,\n Session,\n SessionInfo,\n SignInResult,\n TokenStorage,\n TotpEnrollment,\n TwoFactorChallenge,\n TwoFactorToken,\n} from \"./types\";\n\nexport interface AuthNamespaceOptions {\n readonly transport: Transport;\n readonly storage: TokenStorage;\n /** Called after the SDK signs out for any reason (manual, refresh failure). */\n readonly onSignedOut?: () => void;\n}\n\nexport interface AuthNamespace {\n readonly signUp: (input: { email: string; password: string; name: string }) => Promise<Session>;\n readonly signIn: (input: { email: string; password: string }) => Promise<SignInResult>;\n readonly signOut: () => Promise<void>;\n readonly getSession: () => Session | null;\n readonly onAuthStateChange: (listener: (state: AuthState) => void) => () => void;\n readonly requestPasswordReset: (input: { email: string }) => Promise<void>;\n readonly confirmPasswordReset: (input: { token: string; password: string }) => Promise<void>;\n readonly confirmEmail: (input: { token: string }) => Promise<void>;\n /**\n * Mail a new verification link to the signed-in member's own address. A\n * bearer call: a 401 refreshes or signs out as usual.\n */\n readonly resendVerification: () => Promise<ResendVerificationResult>;\n readonly verifyTotp: (input: { challenge: TwoFactorChallenge; code: string }) => Promise<Session>;\n readonly verifyBackupCode: (input: {\n challenge: TwoFactorChallenge;\n code: string;\n }) => Promise<Session>;\n readonly requestEmailOtp: (input: { challenge: TwoFactorChallenge }) => Promise<void>;\n readonly verifyEmailOtp: (input: {\n challenge: TwoFactorChallenge;\n code: string;\n }) => Promise<Session>;\n readonly getSecurityStatus: () => Promise<SecurityStatus>;\n readonly enableTotp: (input: { password: string }) => Promise<TotpEnrollment>;\n readonly verifyTotpSetup: (input: { code: string }) => Promise<void>;\n readonly disableTwoFactor: (input: { password: string }) => Promise<void>;\n readonly regenerateBackupCodes: (input: { password: string }) => Promise<readonly string[]>;\n readonly changePassword: (input: {\n currentPassword: string;\n newPassword: string;\n revokeOtherSessions?: boolean;\n }) => Promise<void>;\n readonly changeEmail: (input: { newEmail: string }) => Promise<void>;\n /**\n * Confirm an email change with the token from the confirmation link, for an\n * app that opens that link itself (Fytkit Coach's app link). No session\n * needed: the emailed token is the proof.\n */\n readonly confirmEmailChange: (input: { token: string }) => Promise<void>;\n readonly listSessions: () => Promise<readonly SessionInfo[]>;\n readonly revokeSession: (input: { revokeId: string }) => Promise<void>;\n readonly revokeOtherSessions: () => Promise<void>;\n readonly closeAccount: (input: { confirmEmail: string; password: string }) => Promise<void>;\n /** Internal: force a refresh now. Wired by transport's 401 retry path. */\n readonly _refresh: () => Promise<Session>;\n /** Internal: clear cached session + emit signed_out. */\n readonly _signOutLocal: () => void;\n}\n\n/**\n * Schedule a proactive refresh this many ms before `expiresAt`. The token\n * is a server session (7 days, extended on use), and a 60-second lead time\n * refreshes it transparently in long-running tabs without refreshing\n * needlessly.\n */\nconst PROACTIVE_REFRESH_LEAD_MS = 60_000;\n\n/**\n * When a proactive refresh fails for a reason other than the server rejecting\n * the token (offline, 5xx, rate limit), the session is kept and the timer\n * retries with exponential backoff: 5 s, 10 s, 20 s … capped at 5 min, for\n * at most this many attempts. After that the SDK stops polling; the next\n * request's reactive 401 path takes over if the token has actually lapsed.\n */\nconst REFRESH_RETRY_BASE_MS = 5_000;\nconst REFRESH_RETRY_MAX_MS = 5 * 60_000;\nconst REFRESH_MAX_RETRIES = 6;\n\n/**\n * Only an explicit server rejection of the token (401/403) ends the session.\n * Network failures, 5xx and rate limits say nothing about the token, so\n * signing the member out on them would be wrong — and on a phone that drops\n * signal it signs members out constantly.\n */\nfunction isSessionRejected(err: FytkitError): boolean {\n return err.kind === \"auth_required\" || err.kind === \"forbidden\";\n}\n\nfunction refreshRetryDelay(attempt: number, err: FytkitError): number {\n const backoff = Math.min(REFRESH_RETRY_MAX_MS, REFRESH_RETRY_BASE_MS * 2 ** attempt);\n // Honour the server's rate-limit window when it is longer than our backoff.\n return err.kind === \"rate_limited\" ? Math.max(backoff, err.retryAfter) : backoff;\n}\n\n/**\n * The auth namespace owns the cached session. It is the single writer to\n * `storage`; everything else reads via `getSession`. Calls that mutate\n * the session also emit on the event bus and (re)schedule the proactive\n * refresh timer against the new expiry.\n */\nexport function createAuthNamespace(opts: AuthNamespaceOptions): AuthNamespace {\n const bus = createEventBus<AuthState>();\n let cached: Session | null = null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n // Forward declaration so scheduleRefresh can call the same _refresh body\n // the public surface exposes. Assigned at the bottom of the factory.\n let refreshFn: () => Promise<Session> = () => {\n throw new Error(\"refreshFn called before initialization\");\n };\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n // `setTimeout` clamps any delay > 2^31-1 ms (~24.8 days) to 1 ms and\n // prints a Node deprecation warning. Sessions with an exotic far-future\n // expiry (test fixtures, mis-configured server) would otherwise fire the\n // timer instantly; cap the delay to the safe upper bound so the timer\n // simply re-arms on its next firing.\n const MAX_TIMEOUT_MS = 2_147_483_647;\n\n // `attempt` counts consecutive failed proactive refreshes; 0 for the\n // expiry-driven firing, 1+ for backoff retries.\n function armRefreshTimer(delay: number, attempt: number): void {\n clearTimer();\n timer = setTimeout(() => {\n timer = null;\n void refreshFn().catch((err: FytkitError) => {\n // A rejected token already cleared the session inside _refresh.\n // Otherwise keep it and retry — unless something re-armed the timer\n // meanwhile (a sign-in, a rotated token) or we are out of attempts.\n if (cached === null || timer !== null || attempt >= REFRESH_MAX_RETRIES) return;\n armRefreshTimer(refreshRetryDelay(attempt, err), attempt + 1);\n });\n }, delay);\n }\n\n function scheduleRefresh(session: Session): void {\n clearTimer();\n const expiresMs = new Date(session.expiresAt).getTime();\n if (Number.isNaN(expiresMs)) return; // bad date — bail; reactive 401 path will handle it\n const rawDelay = expiresMs - Date.now() - PROACTIVE_REFRESH_LEAD_MS;\n armRefreshTimer(Math.min(MAX_TIMEOUT_MS, Math.max(0, rawDelay)), 0);\n }\n\n function applySession(session: Session, event: \"signed_in\" | \"session_refreshed\"): Session {\n cached = session;\n void opts.storage.set(session);\n bus.emit({ type: event, session });\n scheduleRefresh(session);\n return session;\n }\n\n function clearSession(emit = true): void {\n cached = null;\n clearTimer();\n void opts.storage.remove();\n if (emit) bus.emit({ type: \"signed_out\" });\n opts.onSignedOut?.();\n }\n\n async function call<T>(thunk: () => Promise<T>): Promise<T> {\n try {\n return await thunk();\n } catch (err) {\n throw normalizeError(err);\n }\n }\n\n // Best-effort hydrate at construction. If the storage already holds a\n // session (warm reload, second tab), restore it and arm the timer.\n void Promise.resolve(opts.storage.get()).then((s) => {\n if (s) {\n cached = s;\n scheduleRefresh(s);\n }\n });\n\n const signUp: AuthNamespace[\"signUp\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.signUp(input));\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n const signIn: AuthNamespace[\"signIn\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.signIn(input));\n if (result.data.kind === \"two_factor_required\") {\n // Mint the branded token exactly once at the network boundary.\n const token = result.data.twoFactorToken as TwoFactorToken;\n return {\n kind: \"two_factor_required\",\n challenge: { token, availableMethods: result.data.availableMethods },\n };\n }\n if (result.data.kind === \"signed_in\") {\n const { user, session } = result.data;\n const cached = applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n return { kind: \"signed_in\", session: cached };\n }\n // Exhaustiveness guard — a future server-side variant must not silently fall through.\n const _exhaustive: never = result.data;\n throw _exhaustive;\n };\n\n const signOut: AuthNamespace[\"signOut\"] = async () => {\n const current = cached;\n if (!current) return;\n try {\n await opts.transport.client.auth.signOut({ token: current.accessToken });\n } catch (err) {\n // Even if the server rejects (e.g. token already revoked), clear\n // the local cache so the user is signed out client-side.\n void err;\n }\n clearSession();\n };\n\n const getSession: AuthNamespace[\"getSession\"] = () => cached;\n const onAuthStateChange: AuthNamespace[\"onAuthStateChange\"] = (listener) => bus.on(listener);\n\n const requestPasswordReset: AuthNamespace[\"requestPasswordReset\"] = async (input) => {\n await call(() => opts.transport.client.auth.requestPasswordReset(input));\n };\n const confirmPasswordReset: AuthNamespace[\"confirmPasswordReset\"] = async (input) => {\n await call(() => opts.transport.client.auth.confirmPasswordReset(input));\n };\n const confirmEmail: AuthNamespace[\"confirmEmail\"] = async (input) => {\n await call(() => opts.transport.client.auth.confirmEmail(input));\n };\n\n const resendVerification: AuthNamespace[\"resendVerification\"] = async () => {\n const result = await call(() => opts.transport.client.auth.resendVerification({}));\n return result.data;\n };\n\n // --- Two-factor sign-in completion -----------------------------------------\n //\n // Each consumes the challenge minted by `signIn` and, on success, caches the\n // issued session exactly like the signed_in branch of `signIn`.\n\n const verifyTotp: AuthNamespace[\"verifyTotp\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.verifyTotp({ token: input.challenge.token, code: input.code }),\n );\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n const verifyBackupCode: AuthNamespace[\"verifyBackupCode\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.verifyBackupCode({\n token: input.challenge.token,\n code: input.code,\n }),\n );\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n const requestEmailOtp: AuthNamespace[\"requestEmailOtp\"] = async (input) => {\n await call(() => opts.transport.client.auth.requestEmailOtp({ token: input.challenge.token }));\n };\n\n const verifyEmailOtp: AuthNamespace[\"verifyEmailOtp\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.verifyEmailOtp({ token: input.challenge.token, code: input.code }),\n );\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"signed_in\",\n );\n };\n\n /**\n * The server rotated the calling session and returned the replacement token\n * (TOTP-setup verification and password change with revokeOtherSessions do\n * this). Swap the cached token immediately — the old bearer is already dead,\n * so any request between now and the swap would 401. `expiresAt` is carried\n * over from the old session; it is at worst *earlier* than the new session's\n * real expiry, so the proactive refresh timer fires early and corrects it.\n */\n function adoptRotatedToken(accessToken: string): void {\n const current = cached;\n if (!current || !accessToken || accessToken === current.accessToken) return;\n applySession({ ...current, accessToken }, \"session_refreshed\");\n }\n\n const getSecurityStatus: AuthNamespace[\"getSecurityStatus\"] = async () => {\n const result = await call(() => opts.transport.client.auth.getSecurityStatus());\n return result.data;\n };\n\n const enableTotp: AuthNamespace[\"enableTotp\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.enableTotp(input));\n return result.data;\n };\n\n const verifyTotpSetup: AuthNamespace[\"verifyTotpSetup\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.verifyTotpSetup(input));\n adoptRotatedToken(result.data.accessToken);\n };\n\n const disableTwoFactor: AuthNamespace[\"disableTwoFactor\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.disableTwoFactor(input));\n adoptRotatedToken(result.data.accessToken);\n };\n\n const regenerateBackupCodes: AuthNamespace[\"regenerateBackupCodes\"] = async (input) => {\n const result = await call(() => opts.transport.client.auth.regenerateBackupCodes(input));\n return result.data.backupCodes;\n };\n\n // --- Account security -------------------------------------------------------\n\n const changePassword: AuthNamespace[\"changePassword\"] = async (input) => {\n const result = await call(() =>\n opts.transport.client.auth.changePassword({\n currentPassword: input.currentPassword,\n newPassword: input.newPassword,\n revokeOtherSessions: input.revokeOtherSessions,\n }),\n );\n adoptRotatedToken(result.data.accessToken);\n };\n\n const changeEmail: AuthNamespace[\"changeEmail\"] = async (input) => {\n await call(() => opts.transport.client.auth.changeEmail(input));\n };\n\n const confirmEmailChange: AuthNamespace[\"confirmEmailChange\"] = async (input) => {\n await call(() => opts.transport.client.auth.confirmEmailChange(input));\n };\n\n const listSessions: AuthNamespace[\"listSessions\"] = async () => {\n const result = await call(() => opts.transport.client.auth.listSessions());\n return result.data;\n };\n\n const revokeSession: AuthNamespace[\"revokeSession\"] = async (input) => {\n await call(() => opts.transport.client.auth.revokeSession(input));\n };\n\n const revokeOtherSessions: AuthNamespace[\"revokeOtherSessions\"] = async () => {\n await call(() => opts.transport.client.auth.revokeOtherSessions());\n };\n\n // The server deletes the login, so the cached bearer is dead the moment\n // this resolves — drop it locally (emits signed_out). On failure (wrong\n // password, network) the session is untouched and the error propagates.\n const closeAccount: AuthNamespace[\"closeAccount\"] = async (input) => {\n await call(() =>\n opts.transport.client.auth.closeAccount({\n confirmEmail: input.confirmEmail,\n password: input.password,\n }),\n );\n clearSession();\n };\n\n const _refresh: AuthNamespace[\"_refresh\"] = async () => {\n // Re-read storage as a fallback so a session written after\n // construction (e.g. on tab restore or in tests) is honoured.\n let current = cached;\n if (!current) {\n const fromStorage = await Promise.resolve(opts.storage.get());\n if (fromStorage) {\n cached = fromStorage;\n current = fromStorage;\n }\n }\n if (!current) {\n const err: FytkitError = { kind: \"auth_required\" };\n throw err;\n }\n try {\n const result = await opts.transport.client.auth.refresh({ token: current.accessToken });\n const { user, session } = result.data;\n return applySession(\n { accessToken: session.accessToken, expiresAt: session.expiresAt, user },\n \"session_refreshed\",\n );\n } catch (err) {\n const normalized = normalizeError(err);\n // Sign out only when the server actually rejected the token. A network\n // or server failure leaves the session in place for a later retry.\n if (isSessionRejected(normalized)) clearSession();\n throw normalized;\n }\n };\n\n const _signOutLocal: AuthNamespace[\"_signOutLocal\"] = () => clearSession();\n\n refreshFn = _refresh;\n\n return {\n signUp,\n signIn,\n signOut,\n getSession,\n onAuthStateChange,\n requestPasswordReset,\n confirmPasswordReset,\n confirmEmail,\n resendVerification,\n verifyTotp,\n verifyBackupCode,\n requestEmailOtp,\n verifyEmailOtp,\n getSecurityStatus,\n enableTotp,\n verifyTotpSetup,\n disableTwoFactor,\n regenerateBackupCodes,\n changePassword,\n changeEmail,\n confirmEmailChange,\n listSessions,\n revokeSession,\n revokeOtherSessions,\n closeAccount,\n _refresh,\n _signOutLocal,\n };\n}\n","// Internal: not exported from the package.\n\n/** Where the SDK calls fytkit unless `baseUrl` says otherwise. */\nexport const DEFAULT_BASE_URL = \"https://api.fytkit.com\";\n","import { normalizeError } from \"./error\";\nimport type { Transport } from \"./transport\";\n\n/**\n * Wrap every method on a public-router slice so thrown ORPCErrors become\n * normalised FytkitErrors at the SDK boundary.\n *\n * The slice we receive is an `@orpc/client` Proxy whose own keys are not\n * enumerable — every property lookup is intercepted lazily. We mirror that\n * shape by returning a Proxy of our own that wraps each function on first\n * access and caches the result. This way the SDK never needs to know the\n * method names ahead of time, and the typed shape from the server router\n * flows through unchanged.\n */\nfunction wrapSlice<T extends object>(slice: T): T {\n const cache = new Map<string | symbol, unknown>();\n return new Proxy(slice, {\n get(target, prop, receiver) {\n if (cache.has(prop)) return cache.get(prop);\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== \"function\") {\n // Allow nested namespaces to flow through unchanged (the orpc\n // proxy returns another proxy here, not a plain object).\n return value;\n }\n const wrapped = async (...args: unknown[]) => {\n try {\n return await (value as (...a: unknown[]) => Promise<unknown>)(...args);\n } catch (err) {\n throw normalizeError(err);\n }\n };\n cache.set(prop, wrapped);\n return wrapped;\n },\n }) as T;\n}\n\nexport interface DataNamespaces {\n readonly workouts: Transport[\"client\"][\"workout\"];\n readonly exercises: Transport[\"client\"][\"exercise\"];\n readonly programs: Transport[\"client\"][\"program\"];\n readonly goals: Transport[\"client\"][\"goal\"];\n readonly equipment: Transport[\"client\"][\"equipment\"];\n readonly recipes: Transport[\"client\"][\"recipe\"];\n readonly nutrition: Transport[\"client\"][\"nutrition\"];\n readonly articles: Transport[\"client\"][\"article\"];\n readonly enrollments: Transport[\"client\"][\"enrollment\"];\n readonly favourites: Transport[\"client\"][\"favourite\"];\n readonly sessionLogs: Transport[\"client\"][\"sessionLog\"];\n readonly progression: Transport[\"client\"][\"progression\"];\n readonly bodyMetrics: Transport[\"client\"][\"bodyMetric\"];\n readonly bodyMeasurements: Transport[\"client\"][\"bodyMeasurement\"];\n readonly progressPhotos: Transport[\"client\"][\"progressPhoto\"];\n readonly checkIns: Transport[\"client\"][\"checkin\"];\n readonly messages: Transport[\"client\"][\"message\"];\n readonly timeline: Transport[\"client\"][\"timeline\"];\n readonly health: Transport[\"client\"][\"health\"];\n readonly habits: Transport[\"client\"][\"habit\"];\n readonly progress: Transport[\"client\"][\"progress\"];\n readonly media: Transport[\"client\"][\"media\"];\n readonly subscription: Transport[\"client\"][\"subscription\"];\n readonly profile: Transport[\"client\"][\"profile\"];\n readonly nutritionLog: Transport[\"client\"][\"nutritionLog\"];\n readonly preferences: Transport[\"client\"][\"preferences\"];\n}\n\nexport function createDataNamespaces(transport: Transport): DataNamespaces {\n const c = transport.client;\n return {\n workouts: wrapSlice(c.workout),\n exercises: wrapSlice(c.exercise),\n programs: wrapSlice(c.program),\n goals: wrapSlice(c.goal),\n equipment: wrapSlice(c.equipment),\n recipes: wrapSlice(c.recipe),\n nutrition: wrapSlice(c.nutrition),\n articles: wrapSlice(c.article),\n enrollments: wrapSlice(c.enrollment),\n favourites: wrapSlice(c.favourite),\n sessionLogs: wrapSlice(c.sessionLog),\n progression: wrapSlice(c.progression),\n bodyMetrics: wrapSlice(c.bodyMetric),\n bodyMeasurements: wrapSlice(c.bodyMeasurement),\n progressPhotos: wrapSlice(c.progressPhoto),\n checkIns: wrapSlice(c.checkin),\n messages: wrapSlice(c.message),\n timeline: wrapSlice(c.timeline),\n health: wrapSlice(c.health),\n habits: wrapSlice(c.habit),\n progress: wrapSlice(c.progress),\n media: wrapSlice(c.media),\n subscription: wrapSlice(c.subscription),\n profile: wrapSlice(c.profile),\n nutritionLog: wrapSlice(c.nutritionLog),\n preferences: wrapSlice(c.preferences),\n };\n}\n","import type { Session, TokenStorage } from \"./types\";\n\nconst STORAGE_KEY_PREFIX = \"fytkit:\";\nconst STORAGE_KEY_SUFFIX = \":tokens\";\n\nfunction keyFor(clientId: string): string {\n return `${STORAGE_KEY_PREFIX}${clientId}${STORAGE_KEY_SUFFIX}`;\n}\n\nfunction isValidSession(value: unknown): value is Session {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.accessToken === \"string\" &&\n typeof v.expiresAt === \"string\" &&\n typeof v.user === \"object\" &&\n v.user !== null &&\n typeof (v.user as { id?: unknown }).id === \"string\" &&\n typeof (v.user as { email?: unknown }).email === \"string\"\n );\n}\n\n/**\n * Default web storage. Reads/writes JSON under `fytkit:{clientId}:tokens`.\n * Silently no-ops when localStorage throws (Safari private mode, SSR).\n */\nexport function createLocalStorageAdapter(clientId: string): TokenStorage {\n const key = keyFor(clientId);\n return {\n get: () => {\n try {\n const raw = globalThis.localStorage?.getItem(key);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n return isValidSession(parsed) ? parsed : null;\n } catch {\n return null;\n }\n },\n set: (session: Session) => {\n try {\n globalThis.localStorage?.setItem(key, JSON.stringify(session));\n } catch {\n // Quota / private-mode / SSR — swallow and let the caller treat the\n // session as in-memory-only for this tab.\n }\n },\n remove: () => {\n try {\n globalThis.localStorage?.removeItem(key);\n } catch {\n // see set() rationale\n }\n },\n };\n}\n\n/** In-memory storage. Useful for tests and SSR. */\nexport function createMemoryStorageAdapter(): TokenStorage {\n let current: Session | null = null;\n return {\n get: () => current,\n set: (session: Session) => {\n current = session;\n },\n remove: () => {\n current = null;\n },\n };\n}\n","// The `Retry-After` fallback for 429s (SDK1, #461).\n//\n// The SDK reads a 429's wait from its error data (`data.retryAfterMs`), which\n// every fytkit 429 sends. A 429 that doesn't carry it (an older server's\n// plain body, a proxy or CDN in front of the API) used to reach an app as\n// `retryAfter: 0`, so a retry loop retried at once. When such a 429 has a\n// `Retry-After` header, the transport writes the header's wait into the\n// error data before oRPC reads the response, so `normalizeError` finds it\n// where it always looks. A hint the data already carries always wins.\n\nimport { isRecord } from \"./is-record\";\n\nconst DELAY_SECONDS = /^\\d+$/;\n\n/**\n * A `Retry-After` value's wait in milliseconds from `now`: delay-seconds\n * (`\"120\"`) or an HTTP date (zero once it has passed). Null when there is no\n * value or it is neither.\n */\nexport function retryAfterHeaderMs(value: string | null, now: number): number | null {\n if (value === null) return null;\n const trimmed = value.trim();\n if (trimmed.length === 0) return null;\n if (DELAY_SECONDS.test(trimmed)) return Number(trimmed) * 1000;\n // Only a date in letters: Date.parse also accepts bare numbers like \"1.5\".\n if (!/[a-z]/i.test(trimmed)) return null;\n const at = Date.parse(trimmed);\n return Number.isNaN(at) ? null : Math.max(0, at - now);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The 429's body with `waitMs` as its error data's `retryAfterMs`, as oRPC's\n * RPC protocol frames it (`{ json: <error> }`); null when the data already\n * carries a hint. A body that isn't an oRPC error becomes the error oRPC\n * would have sent.\n */\nfunction hintedBody(body: unknown, waitMs: number): Readonly<Record<string, unknown>> | null {\n if (isRecord(body) && isRecord(body.json) && typeof body.json.code === \"string\") {\n const error = body.json;\n const data = isRecord(error.data) ? error.data : {};\n if (typeof data.retryAfterMs === \"number\") return null;\n return { ...body, json: { ...error, data: { ...data, retryAfterMs: waitMs } } };\n }\n return {\n json: {\n defined: false,\n code: \"TOO_MANY_REQUESTS\",\n status: 429,\n message: \"Too Many Requests\",\n data: { retryAfterMs: waitMs },\n },\n };\n}\n\n/**\n * `response` with the `Retry-After` header's wait written into its error data\n * when it is a 429 whose data has no `retryAfterMs`; unchanged otherwise.\n */\nexport async function withRetryAfterHint(response: Response, now: number): Promise<Response> {\n if (response.status !== 429) return response;\n const waitMs = retryAfterHeaderMs(response.headers.get(\"retry-after\"), now);\n if (waitMs === null) return response;\n const hinted = hintedBody(parseJson(await response.clone().text()), waitMs);\n if (hinted === null) return response;\n const headers = new Headers(response.headers);\n headers.set(\"content-type\", \"application/json\");\n headers.delete(\"content-length\");\n return new Response(JSON.stringify(hinted), {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n","import type { PublicRouter } from \"@fytkit/types\";\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { RouterClient } from \"@orpc/server\";\nimport { withRetryAfterHint } from \"./retry-after\";\nimport type { Session } from \"./types\";\n\nexport interface CreateTransportOptions {\n readonly baseUrl: string;\n readonly clientId: string;\n readonly getSession: () => Session | null;\n readonly fetch?: typeof fetch;\n}\n\n/**\n * Per-call options the SDK's own wrappers pass to the link. `ifNoneMatch`\n * becomes the request's `If-None-Match` header (`app.bootstrap`'s ETag\n * revalidation, #535); every other call sends none.\n */\nexport interface TransportContext {\n readonly ifNoneMatch?: string;\n}\n\n/** The public router's client, taking {@link TransportContext} per call. */\nexport type TransportClient = RouterClient<PublicRouter, TransportContext>;\n\nexport interface Transport {\n readonly client: TransportClient;\n /** Internal: hook for attachRefreshInterceptor to install a fetch wrapper. */\n readonly _setFetchInterceptor: (interceptor: FetchInterceptor) => void;\n}\n\ntype FetchInterceptor = (\n request: Request,\n next: (req: Request) => Promise<Response>,\n) => Promise<Response>;\n\nexport function createTransport(options: CreateTransportOptions): Transport {\n let interceptor: FetchInterceptor | null = null;\n const baseFetch: typeof fetch = options.fetch ?? fetch.bind(globalThis);\n\n // The fetch handed to RPCLink resolves the interceptor lazily so callers\n // can install one after construction (see attachRefreshInterceptor). A 429\n // whose error data lacks a retry hint gets the `Retry-After` header's wait\n // written in (retry-after.ts), so `rate_limited.retryAfter` reads it.\n const wrappedFetch: typeof fetch = async (input, init) => {\n const request = input instanceof Request ? input : new Request(input, init);\n const next = (req: Request) => baseFetch(req);\n const response = interceptor ? await interceptor(request, next) : await next(request);\n return withRetryAfterHint(response, Date.now());\n };\n\n const link = new RPCLink<TransportContext>({\n url: () => `${options.baseUrl}/rpc`,\n headers: ({ context }) => {\n const session = options.getSession();\n const headers: Record<string, string> = {\n \"X-Fytkit-Client-Id\": options.clientId,\n };\n if (session) {\n headers.Authorization = `Bearer ${session.accessToken}`;\n }\n if (context.ifNoneMatch !== undefined) {\n headers[\"If-None-Match\"] = context.ifNoneMatch;\n }\n return headers;\n },\n fetch: wrappedFetch,\n });\n\n const client: TransportClient = createORPCClient(link);\n return {\n client,\n _setFetchInterceptor: (i) => {\n interceptor = i;\n },\n };\n}\n\n/** Internal marker header — set on the refresh call so we never recurse. */\nconst REFRESH_HEADER = \"X-Fytkit-Refresh\";\n\n/**\n * Procedures that authenticate with credentials in their input (a password,\n * an emailed token, a one-time code or a sign-in challenge), never with the\n * bearer. A 401 from one of them means those credentials were rejected, not\n * that the session is dead, so it passes straight through: no refresh, no\n * replay, no local sign-out. Otherwise a member who mistypes their password\n * while signing in again (after `reauth_required`) would be signed out.\n */\nconst CREDENTIAL_PROCEDURES: readonly string[] = [\n \"auth/signIn\",\n \"auth/signUp\",\n \"auth/requestPasswordReset\",\n \"auth/confirmPasswordReset\",\n \"auth/confirmEmail\",\n \"auth/confirmEmailChange\",\n \"auth/verifyTotp\",\n \"auth/verifyBackupCode\",\n \"auth/requestEmailOtp\",\n \"auth/verifyEmailOtp\",\n];\n\n/**\n * The RPCLink routes per-procedure calls under a URL whose pathname ends\n * with the procedure path (joined as `/segment/segment`), so procedures are\n * detected by suffix. endsWith keeps trailing query strings or link-level\n * prefixes from breaking detection.\n */\nfunction requestPathname(request: Request): string {\n try {\n return new URL(request.url).pathname;\n } catch {\n return \"\";\n }\n}\n\n/** Detect whether a request targets the refresh procedure. */\nfunction isRefreshRequest(request: Request): boolean {\n if (request.headers.get(REFRESH_HEADER) === \"1\") return true;\n return requestPathname(request).endsWith(\"/auth/refresh\");\n}\n\n/** Detect whether a request targets one of the `CREDENTIAL_PROCEDURES`. */\nfunction isCredentialRequest(request: Request): boolean {\n const path = requestPathname(request);\n return CREDENTIAL_PROCEDURES.some((procedure) => path.endsWith(`/${procedure}`));\n}\n\nexport interface AttachRefreshInterceptorOptions {\n readonly transport: Transport;\n readonly auth: {\n readonly _refresh: () => Promise<Session>;\n readonly _signOutLocal: () => void;\n readonly getSession: () => Session | null;\n };\n}\n\n/**\n * Install a fetch interceptor that, on a 401 response, calls auth._refresh\n * once and retries the original request. Concurrent 401s de-duplicate\n * onto a single in-flight refresh promise. A 401/403 from refresh itself or\n * a 401 from the retry triggers a local sign-out and propagates the original\n * 401 to the caller (after which orpc throws ORPCError(\"UNAUTHORIZED\")). A\n * refresh that fails for any other reason (offline, 5xx) also propagates the\n * original 401, but keeps the session so a later call can succeed. A 401 from\n * a credential procedure (`CREDENTIAL_PROCEDURES`) is never refreshed or\n * replayed: it rejects the credentials, not the session.\n */\nexport function attachRefreshInterceptor(opts: AttachRefreshInterceptorOptions): void {\n let inflight: Promise<Session> | null = null;\n\n const refreshOnce = (): Promise<Session> => {\n if (!inflight) {\n inflight = opts.auth._refresh().finally(() => {\n inflight = null;\n });\n }\n return inflight;\n };\n\n opts.transport._setFetchInterceptor(async (request, next) => {\n // Refresh calls must never trigger a recursive refresh, and a 401 from a\n // credential procedure is a wrong password or code, not a dead session.\n if (isRefreshRequest(request) || isCredentialRequest(request)) {\n return next(request);\n }\n\n // Capture the request body now, before we pass `request` to `next` —\n // once fetch consumes a Request body it can no longer be cloned.\n const buffered = await request.clone().arrayBuffer();\n\n const firstResponse = await next(request);\n if (firstResponse.status !== 401) return firstResponse;\n\n try {\n await refreshOnce();\n } catch {\n // _refresh decides whether the session survives: it signs out only\n // when the server rejected the token, not on a network/server failure.\n return firstResponse;\n }\n\n // Rebuild headers so the new Authorization header replaces the stale\n // one — the RPCLink only re-evaluates its own `headers()` once per call,\n // so we patch the bearer ourselves here.\n const replayedHeaders = new Headers(request.headers);\n const session = opts.auth.getSession();\n if (session) {\n replayedHeaders.set(\"Authorization\", `Bearer ${session.accessToken}`);\n } else {\n replayedHeaders.delete(\"Authorization\");\n }\n const replayed = new Request(request.url, {\n method: request.method,\n headers: replayedHeaders,\n body: buffered.byteLength > 0 ? buffered : undefined,\n credentials: request.credentials,\n mode: request.mode,\n cache: request.cache,\n redirect: request.redirect,\n referrer: request.referrer,\n integrity: request.integrity,\n });\n\n const retried = await next(replayed);\n if (retried.status === 401) {\n // Refresh succeeded server-side but the server still rejects — the\n // session is dead. Sign out locally and let orpc surface the 401.\n opts.auth._signOutLocal();\n }\n return retried;\n });\n}\n","// Resolving a studio's join code (#529): the one call an app makes before\n// it has a client id. The code on a studio's Fytkit Coach QR code or join\n// link names the studio; this turns it into the client id to pass to\n// `createFytkitClient`, with what a welcome screen needs.\n//\n// A plain `GET /api/v1/join/{code}`: it takes no credentials and needs no\n// client, so it is a function of its own rather than a client method.\n\nimport { createORPCErrorFromJson, isORPCErrorJson } from \"@orpc/client\";\nimport { DEFAULT_BASE_URL } from \"./base-url\";\nimport { normalizeError } from \"./error\";\nimport { isRecord } from \"./is-record\";\nimport { retryAfterHeaderMs } from \"./retry-after\";\nimport type { FytkitError } from \"./types\";\n\n/** Options for {@link resolveJoinCode}. */\nexport interface ResolveJoinCodeOptions {\n /** The API's origin, as for `createFytkitClient`. Defaults to `https://api.fytkit.com`. */\n readonly baseUrl?: string;\n /** A `fetch` to use instead of the global one. */\n readonly fetch?: typeof fetch;\n}\n\n/**\n * The design the app paints for the studio: its Fytkit Coach theme (an\n * app-only one if the studio picked one, otherwise its published site design),\n * the theme's palette and the shades derived from it. Every colour is a\n * `#rrggbb` hex string.\n */\nexport interface JoinCodeTheme {\n /** The site template the design comes from (the app calls it the theme). */\n readonly templateId: string;\n /** The template's colour theme (the app calls it the palette). */\n readonly themeId: string;\n /** The theme's curated colours. */\n readonly palette: {\n readonly primary: string;\n readonly secondary: string;\n readonly accent: string;\n readonly background: string;\n readonly text: string;\n };\n /** Shades derived from the palette, for states and text on fills. */\n readonly variants: {\n /** Hover/press shade of `primary`. */\n readonly primaryDeep: string;\n /** Tinted background of `primary`. */\n readonly primarySoft: string;\n /** Text that reads on `primary`. */\n readonly onPrimary: string;\n /** Hover/press shade of `accent`. */\n readonly accentDeep: string;\n /** Tinted background of `accent`. */\n readonly accentSoft: string;\n /** Text that reads on `accent`. */\n readonly onAccent: string;\n /** The highlight fill, legible under dark ink text. */\n readonly highlight: string;\n /** Hover/press shade of `highlight`. */\n readonly highlightDeep: string;\n /** Tinted background of `highlight`. */\n readonly highlightSoft: string;\n };\n}\n\n/** A coach a join code's studio names publicly. */\nexport interface JoinCodeCoach {\n /** The coach's public name. */\n readonly name: string;\n /** The coach's public profile image, a stable URL; null when there is none. */\n readonly avatarUrl: string | null;\n}\n\n/** What a join code resolves to. */\nexport interface JoinCodeResolution {\n /** The code in canonical form: upper case, no spaces or hyphens. */\n readonly code: string;\n /** The studio's Fytkit Coach client id: pass it to `createFytkitClient`. */\n readonly clientId: string;\n /** What the welcome screen names and shows. */\n readonly studio: {\n /** The name members know the studio by. */\n readonly name: string;\n /** The studio's logo, a stable public URL; null when it has none. */\n readonly logoUrl: string | null;\n /**\n * Who coaches there, as the studio's public site shows them: today one\n * entry, the studio's own coach profile (never staff). At most 3.\n */\n readonly coaches: readonly JoinCodeCoach[];\n /** Published programmes, any access level; drafts and archived ones never count. */\n readonly programCount: number;\n };\n /** The design the app paints for the studio. */\n readonly theme: JoinCodeTheme;\n}\n\n/**\n * What the server answers for an unknown or revoked code, and what this\n * answers itself, without a request, for input that can't safely go in the\n * URL path.\n */\nconst INACTIVE: FytkitError = {\n kind: \"not_found\",\n refusal: { reason: \"join_code_inactive\", data: {} },\n};\n\n/**\n * Whitespace, the hyphen-minus and the dashes phones substitute for it: the\n * separators the server ignores too (src/lib/join-code/code.ts in\n * fytkit-web, which a test there checks this against).\n */\nconst IGNORED_SEPARATORS = /[\\s\\-\\u2010-\\u2015\\u2212]/g;\n\n/**\n * Only what keeps the URL path safe: letters and digits, at most as many as\n * the server reads. The code's alphabet and length are the server's to\n * decide, so a format change never needs a new app build.\n */\nconst PATH_SAFE_CODE = /^[A-Za-z0-9]{1,64}$/;\n\n/** The code to put in the path, upper-cased; null when it isn't path-safe. */\nfunction pathSafeJoinCode(input: string): string | null {\n const candidate = input.replace(IGNORED_SEPARATORS, \"\");\n return PATH_SAFE_CODE.test(candidate) ? candidate.toUpperCase() : null;\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n}\n\n/** The refusal in a non-2xx response, as every SDK method reports one. */\nfunction refusalOf(response: Response, body: unknown): FytkitError {\n const wait = () => retryAfterHeaderMs(response.headers.get(\"retry-after\"), Date.now()) ?? 0;\n // A 404 that isn't the route's own oRPC answer comes from a wrong\n // `baseUrl` or a proxy, not from the code: it stays a plain server error\n // (below), so a member is never told their code is dead because of it.\n if (isORPCErrorJson(body)) {\n const error = normalizeError(createORPCErrorFromJson(body));\n // A 429 without the hint in its data (a proxy's): read the header instead.\n if (error.kind === \"rate_limited\" && error.retryAfter === 0) {\n return { ...error, retryAfter: wait() };\n }\n return error;\n }\n if (response.status === 429) return { kind: \"rate_limited\", retryAfter: wait() };\n return { kind: \"server\", status: response.status };\n}\n\n/**\n * Resolves a studio's join code, as its QR code or join link carries it or a\n * member types it, to the studio's Fytkit Coach client id, name, logo, app\n * theme, public coach profile and published programme count. Needs no client\n * id or session. Codes are case-insensitive and spaces and hyphens are\n * ignored, so pass what the member typed as it is.\n *\n * Rejects with a `FytkitError`, as every SDK method does:\n * - `{ kind: \"not_found\", refusal: { reason: \"join_code_inactive\" } }`: no\n * studio has that code, the studio replaced it with a new one, or the\n * input has something other than letters, digits, spaces and hyphens, or\n * more than 64 letters and digits (separators are stripped first; then\n * without a request). Show\n * \"This code is no longer active. Ask your coach for a new one.\" The\n * cases are deliberately indistinguishable.\n * - `{ kind: \"rate_limited\", retryAfter }`: 60 lookups a minute per IP\n * address; wait `retryAfter` milliseconds.\n * - `{ kind: \"network\" }` when the request never reached the server.\n * - `{ kind: \"server\", status }` for anything else, e.g. a 404 that isn't\n * the API's own answer (a wrong `baseUrl`, or a proxy in front of it).\n *\n * ```ts\n * const studio = await resolveJoinCode(scannedCode);\n * const fytkit = createFytkitClient({ clientId: studio.clientId });\n * ```\n */\nexport async function resolveJoinCode(\n code: string,\n options: ResolveJoinCodeOptions = {},\n): Promise<JoinCodeResolution> {\n // Anything that isn't letters and digits once separators are gone (`/`,\n // `.`, `..`, `?`, `%`) is refused here, so it never reaches the path.\n // Whether the rest is a code at all is the server's call.\n const canonical = pathSafeJoinCode(code);\n if (canonical === null) throw INACTIVE;\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const doFetch: typeof fetch = options.fetch ?? fetch.bind(globalThis);\n let response: Response;\n try {\n response = await doFetch(`${baseUrl}/api/v1/join/${canonical}`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n } catch (error) {\n throw normalizeError(error);\n }\n\n const body = await readJson(response);\n if (response.ok) {\n if (isRecord(body) && isRecord(body.data)) {\n // The server's documented shape, trusted as every other response is.\n return body.data as unknown as JoinCodeResolution;\n }\n throw { kind: \"server\", status: response.status } satisfies FytkitError;\n }\n throw refusalOf(response, body);\n}\n","// \"Subscribe\" or \"What your plan includes\" (#538). Fytkit Coach takes no\n// in-app purchase: where the member's store allows it, the app links out to\n// the studio's Stripe checkout; everywhere else it shows what the plan\n// includes. `app.bootstrap` carries the list of storefronts where linking out\n// is allowed (`data.paymentPolicy`); this reads it for the storefront the\n// device's store reports. It fails closed: an unknown storefront, a missing\n// code or an unmet condition is plan info.\n\nimport type { AppBootstrapData } from \"./app\";\n\n/** `app.bootstrap`'s `data.paymentPolicy`. */\nexport type PaymentPolicy = AppBootstrapData[\"paymentPolicy\"];\n\n/** The store the running app was installed from. */\nexport type PaymentPlatform = PaymentPolicy[\"linkOut\"][number][\"platform\"];\n\n/** Options for {@link resolvePaymentMode}. */\nexport interface ResolvePaymentModeOptions {\n /** The store the running app was installed from: `\"ios\"` or `\"android\"`. */\n readonly platform: PaymentPlatform;\n /**\n * The storefront the store reports, case-insensitive: on iOS StoreKit's\n * `Storefront.current?.countryCode` (ISO 3166-1 alpha-3, e.g. \"USA\"); on\n * Android Play Billing's `BillingConfig.getCountryCode()` (alpha-2, e.g.\n * \"US\"). Pass `null` when the store reports none: that is plan info.\n */\n readonly storefront: string | null | undefined;\n /**\n * The conditions the app meets in this storefront (see `conditions` in\n * `paymentPolicy.linkOut`), e.g. `\"apple_external_purchase_link_entitlement\"`\n * once `ExternalPurchaseLink.canOpen` is true. A storefront whose\n * conditions aren't all met, or names one the app doesn't know, is plan info.\n */\n readonly conditionsMet?: readonly string[];\n}\n\n/**\n * What to show:\n * - `link_out`: \"Subscribe\", opening the studio's checkout outside the app,\n * in the way each of `conditions` requires (none: a plain link to the\n * system browser);\n * - `plan_info`: \"What your plan includes\", with no purchase and no link to one.\n */\nexport type PaymentMode =\n | { readonly mode: \"link_out\"; readonly conditions: readonly string[] }\n | { readonly mode: \"plan_info\" };\n\nconst PLAN_INFO: PaymentMode = { mode: \"plan_info\" };\n\n/**\n * Decides, for the storefront the device's store reports, whether the app\n * shows \"Subscribe\" (a link out) or \"What your plan includes\". Pure: it reads\n * the policy from a stored `app.bootstrap`, so it works offline. A missing or\n * malformed policy (e.g. a snapshot stored before it existed) is plan info.\n *\n * ```ts\n * const { mode } = resolvePaymentMode(data.paymentPolicy, {\n * platform: \"ios\",\n * storefront: storefrontCountryCode, // from StoreKit, e.g. \"USA\"\n * });\n * if (mode === \"link_out\") showSubscribe();\n * else showWhatYourPlanIncludes();\n * ```\n */\nexport function resolvePaymentMode(\n policy: PaymentPolicy | null | undefined,\n options: ResolvePaymentModeOptions,\n): PaymentMode {\n // A snapshot stored before the policy existed, or a malformed one, has no\n // list to read: plan info, as for a storefront it doesn't name.\n if (!policy || !Array.isArray(policy.linkOut)) return PLAN_INFO;\n const storefront = options.storefront?.trim().toUpperCase() ?? \"\";\n if (storefront === \"\") return PLAN_INFO;\n const rule = policy.linkOut.find(\n (entry) =>\n entry?.platform === options.platform &&\n typeof entry.storefront === \"string\" &&\n entry.storefront.toUpperCase() === storefront,\n );\n if (!rule || !Array.isArray(rule.conditions)) return PLAN_INFO;\n const met = new Set(options.conditionsMet ?? []);\n if (!rule.conditions.every((condition) => met.has(condition))) return PLAN_INFO;\n return { mode: \"link_out\", conditions: rule.conditions };\n}\n","// Live updates for a trainee's conversation (SDK1, #461): the third-party\n// counterpart of the member portal's own channel. `messages.realtimeToken`\n// hands out a 15-minute token and the one private Supabase Realtime topic it\n// may join (`member:<client id>`); `app.bootstrap` names the project's public\n// URL and anon key. The database broadcasts `thread_changed` on that topic\n// whenever the conversation changes, with no content: a cue to read\n// `messages.list` and `messages.unreadCount` again, never the data itself.\n// The same topic carries `coach_typing` while a staff member writes to the\n// trainee (W16), surfaced as `onCoachTyping`; only the server sends it.\n//\n// The SDK has no runtime dependency on supabase-js: the app passes a factory\n// that creates a Supabase client, and this module only uses the few members\n// described by `RealtimeClientLike`. Self-contained on purpose (no imports),\n// so the shapes can be checked against supabase-js's own types. The token is\n// set on the socket by hand (`realtime.setAuth(token)`), which supabase-js\n// keeps only from 2.87.1: earlier 2.x clients put the anon key back on the\n// next heartbeat, and the private channel is lost.\n\n/** fytkit's public Supabase Realtime settings, from `app.bootstrap` (`data.realtime`). */\nexport interface RealtimeConnection {\n /** The Supabase project URL. */\n readonly url: string;\n /** The project's public anon key. Safe to ship in an app. */\n readonly anonKey: string;\n}\n\n/** `messages.realtimeToken`'s `data`: a token and the private topic it admits. */\nexport interface MessagesRealtimeGrant {\n /** A Supabase Realtime token for the signed-in trainee, valid until `expiresAt`. */\n readonly token: string;\n /** When the token expires (ISO 8601), 15 minutes after it was issued. */\n readonly expiresAt: string;\n /** The only private topic the token may join: `member:<client id>`. */\n readonly topic: string;\n}\n\n/** An auth-state event, as `auth.onAuthStateChange` reports it; only `type` is read. */\nexport interface MessagesRealtimeAuthEvent {\n /** `signed_in`, `signed_out`, `session_refreshed` or `signed_out_due_to_token_reuse`. */\n readonly type: string;\n}\n\n/** The calls the subscription needs. A `FytkitClient` is one. */\nexport interface MessagesRealtimeSource {\n /**\n * `app.bootstrap`, for the Realtime connection settings. Called without an\n * ETag, so it always answers `ok` with the data.\n */\n readonly app: {\n bootstrap(): PromiseLike<\n | { readonly status: \"ok\"; readonly data: { readonly realtime: RealtimeConnection } }\n | { readonly status: \"unchanged\" }\n >;\n };\n /** `messages.realtimeToken`, for the token and its topic. */\n readonly messages: {\n realtimeToken(\n input: Record<string, never>,\n ): PromiseLike<{ readonly data: MessagesRealtimeGrant }>;\n };\n /**\n * `auth.onAuthStateChange`, optional. When present, a sign-out on the\n * client (`signed_out`, `signed_out_due_to_token_reuse`) ends the\n * subscription at once with `refused`, rather than at its next renewal:\n * the trainee's channel never outlives their session on this client.\n */\n readonly auth?: {\n onAuthStateChange(listener: (event: MessagesRealtimeAuthEvent) => void): () => void;\n };\n}\n\n/**\n * The part of a supabase-js `RealtimeChannel` the subscription uses. A\n * channel that reports `CLOSED` is finished: supabase-js removes it from its\n * socket and never joins it again.\n */\nexport interface RealtimeChannelLike {\n /** Listens for one Broadcast event on the channel. */\n on(\n type: \"broadcast\",\n filter: { readonly event: string },\n callback: (message: unknown) => void,\n ): RealtimeChannelLike;\n /** Joins the channel; `status` is `SUBSCRIBED`, `CHANNEL_ERROR`, `TIMED_OUT` or `CLOSED`. */\n subscribe(callback: (status: string, error?: Error) => void): RealtimeChannelLike;\n}\n\n/**\n * The part of a supabase-js `SupabaseClient` the subscription uses.\n * `@supabase/supabase-js` 2.87.1 or later: earlier clients swap the token the\n * subscription sets for the anon key on the next heartbeat.\n */\nexport interface RealtimeClientLike {\n /** Creates a channel on `name`; the subscription always asks for a private one. */\n channel(\n name: string,\n options: { readonly config: { readonly private: true } },\n ): RealtimeChannelLike;\n /** Leaves and forgets a channel. */\n removeChannel(channel: RealtimeChannelLike): unknown;\n /** The client's Realtime socket: `setAuth(token)` hands it the token. */\n readonly realtime: {\n setAuth(token: string): Promise<void>;\n disconnect(): void;\n };\n}\n\n/**\n * Creates a new Supabase client for the subscription, with\n * `@supabase/supabase-js` 2.87.1 or later:\n *\n * ```ts\n * ({ url, anonKey }) =>\n * createClient(url, anonKey, {\n * auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n * })\n * ```\n *\n * The client carries the trainee's Realtime token and nothing else, so its\n * auth stays off: no stored session, no refresh, and no sign-in read from\n * your page's URL that could replace the token. Return a new client on every\n * call: the subscription sets its own token on the client's socket and\n * disconnects it when it ends, so a client the app uses for anything else\n * would lose both. A factory that throws ends the subscription with\n * `fallback`.\n */\nexport type RealtimeClientFactory = (connection: RealtimeConnection) => RealtimeClientLike;\n\n/**\n * How the conversation is kept current:\n * - `connecting`: before the first answer either way, and while it moves to\n * another trainee's channel. Poll meanwhile.\n * - `live`: the channel is joined; changes arrive as `onChange`. Stop polling.\n * - `fallback`: Realtime is unavailable. Poll at\n * {@link MESSAGES_FALLBACK_POLL_MS}. For now, and `live` may follow, when\n * the token or the connection settings couldn't be fetched (retried every\n * minute), or the channel errored or timed out (supabase-js joins it\n * again by itself), or Realtime closed it (a Realtime restart, a token\n * that expired before its renewal landed: the subscription joins a fresh\n * channel after 5 seconds, then 15, then every minute, with a new token\n * first if the one it holds has expired). For good, and polling carries\n * on, when the server has no live updates to offer (a token without\n * `topic`, no connection settings, or `app.bootstrap` refused) or the\n * factory threw.\n * - `refused`: the token request was refused (`auth_required`,\n * `forbidden`, `not_found` or `validation`), asking again won't change\n * that, and nearly always because there is no trainee to listen for\n * (signed out, account closed, a trainer previewing the portal); or the\n * `FytkitClient` passed in signed out. Final: the subscription has\n * stopped and asks nothing more; the conversation reads are refused the\n * same way.\n */\nexport type MessagesRealtimeStatus = \"connecting\" | \"live\" | \"fallback\" | \"refused\";\n\n/**\n * Whether the trainee's coaching team is writing to them right now, for a\n * \"Sam is writing…\" line under the conversation:\n * - `idle`: nobody is.\n * - `typing`: the staff members in `names` are, by the display name the\n * conversation shows on their messages, each name once, in the order\n * they started. Show the first, or \"Sam and Alex are writing…\".\n *\n * A hint, live only: it is heard only while the status is `live`, and goes\n * back to `idle` when the channel is lost. A name that hears nothing more\n * for 8 seconds ({@link COACH_TYPING_EXPIRY_MS}) drops out by itself, so a\n * lost \"stopped\" never leaves it showing. While polling it stays `idle`.\n */\nexport type CoachTypingState =\n | { readonly kind: \"idle\" }\n | { readonly kind: \"typing\"; readonly names: readonly string[] };\n\n/**\n * How long a staff member stays in {@link CoachTypingState}'s `names` after\n * the last sign that they are writing. The dashboard repeats the sign every\n * 3 seconds while they write.\n */\nexport const COACH_TYPING_EXPIRY_MS = 8_000;\n\n/** What {@link subscribeToMessages} needs besides the client. */\nexport interface SubscribeToMessagesOptions {\n /** Creates the Supabase client the subscription owns. See {@link RealtimeClientFactory}. */\n readonly realtime: RealtimeClientFactory;\n /**\n * The conversation may have changed: read `messages.list` and\n * `messages.unreadCount` again. Also called on every (re)join, since\n * changes while the channel was down were not heard.\n */\n readonly onChange: () => void;\n /** Each change of {@link MessagesRealtimeStatus}, starting with `connecting`. */\n readonly onStatus?: (status: MessagesRealtimeStatus) => void;\n /**\n * Each change of {@link CoachTypingState}: someone on the trainee's\n * coaching team started or stopped writing to them. Optional; never\n * called before a `typing`. A subscription that ends by itself\n * (`refused`, or `fallback` for good) reports `idle` first if anyone was\n * writing; after `unsubscribe()`, nothing is reported.\n */\n readonly onCoachTyping?: (typing: CoachTypingState) => void;\n}\n\n/**\n * How often to poll while the status is `connecting` or `fallback`: the open\n * conversation every 10 seconds, the unread count every 60. Polling spends\n * the app's shared per-minute request budget, so stop while `live`.\n */\nexport const MESSAGES_FALLBACK_POLL_MS = {\n conversation: 10_000,\n unreadCount: 60_000,\n} as const satisfies Readonly<Record<\"conversation\" | \"unreadCount\", number>>;\n\n/** The event the database broadcasts on a trainee's topic. */\nconst THREAD_CHANGED_EVENT = \"thread_changed\";\n/** The event a staff member's typing goes out as, on the same topic. */\nconst COACH_TYPING_EVENT = \"coach_typing\";\n/** The prefix of the only topic a trainee's token admits. */\nconst MEMBER_TOPIC_PREFIX = \"member:\";\n/** Ask for a new token this long before the current one expires. */\nconst TOKEN_RENEW_BEFORE_MS = 60_000;\n/** Never ask again sooner than this, however close the expiry. */\nconst TOKEN_MIN_DELAY_MS = 5_000;\n/** After a request that failed for now, try again this often. */\nconst RETRY_MS = 60_000;\n/**\n * The waits before each fresh join after Realtime closes the channel, from\n * the first closure on; the last repeats until a join subscribes. The same\n * as the member portal's own channel.\n */\nconst REJOIN_DELAYS_MS = [5_000, 15_000, 60_000] as const;\n\n/** Auth events after which there is no trainee on the client to listen for. */\nconst SIGNED_OUT_EVENTS: ReadonlySet<string> = new Set([\n \"signed_out\",\n \"signed_out_due_to_token_reuse\",\n]);\n\n/** Error kinds that asking again won't change: no trainee here, or a bad request. */\nconst FINAL_ERROR_KINDS: ReadonlySet<string> = new Set([\n \"auth_required\",\n \"forbidden\",\n \"not_found\",\n \"validation\",\n]);\n\nfunction isFinalError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null || !(\"kind\" in error)) return false;\n const kind: unknown = error.kind;\n return typeof kind === \"string\" && FINAL_ERROR_KINDS.has(kind);\n}\n\nfunction isMemberTopic(topic: unknown): topic is string {\n return (\n typeof topic === \"string\" &&\n topic.startsWith(MEMBER_TOPIC_PREFIX) &&\n topic.length > MEMBER_TOPIC_PREFIX.length\n );\n}\n\n/**\n * A `coach_typing` broadcast's payload: `{ state: \"typing\" | \"stopped\", name, key }`.\n * `key` tells apart two staff members of one name; a payload without one is\n * keyed by its name.\n */\ninterface CoachTypingSign {\n readonly state: \"typing\" | \"stopped\";\n readonly name: string;\n readonly key: string;\n}\n\n/** Reads a `coach_typing` broadcast, or null when it is not one the server sends. */\nfunction coachTypingSign(message: unknown): CoachTypingSign | null {\n if (typeof message !== \"object\" || message === null || !(\"payload\" in message)) return null;\n const payload: unknown = message.payload;\n if (typeof payload !== \"object\" || payload === null) return null;\n if (!(\"state\" in payload) || !(\"name\" in payload)) return null;\n const { state, name } = payload;\n if (state !== \"typing\" && state !== \"stopped\") return null;\n if (typeof name !== \"string\" || name.length === 0) return null;\n const key = \"key\" in payload && typeof payload.key === \"string\" ? payload.key : \"\";\n return { state, name, key: key.length > 0 ? `key:${key}` : `name:${name}` };\n}\n\nfunction isConnection(value: unknown): value is RealtimeConnection {\n if (typeof value !== \"object\" || value === null) return false;\n if (!(\"url\" in value) || !(\"anonKey\" in value)) return false;\n return (\n typeof value.url === \"string\" &&\n value.url.length > 0 &&\n typeof value.anonKey === \"string\" &&\n value.anonKey.length > 0\n );\n}\n\nfunction renewalDelay(expiresAt: string, now: number): number {\n const expiry = Date.parse(expiresAt);\n if (Number.isNaN(expiry)) return RETRY_MS;\n return Math.max(TOKEN_MIN_DELAY_MS, expiry - TOKEN_RENEW_BEFORE_MS - now);\n}\n\n/** Whether a token still has long enough to run for a join to be worth sending with it. */\nfunction isUsable(expiresAt: string, now: number): boolean {\n return Date.parse(expiresAt) - now > TOKEN_MIN_DELAY_MS;\n}\n\n/** The wait before the fresh join after `closures` closures with no subscription between. */\nfunction rejoinDelay(closures: number): number {\n const last = REJOIN_DELAYS_MS.length - 1;\n return REJOIN_DELAYS_MS[Math.min(Math.max(closures - 1, 0), last)];\n}\n\nfunction ignore(): void {\n // A promise whose outcome nothing waits for.\n}\n\n/**\n * Hands the socket a token. Settles once the socket has it, or once it\n * failed, and never throws: the join follows either way, and a channel\n * without the token reports its own failure.\n */\nfunction authorise(client: RealtimeClientLike, token: string): Promise<void> {\n return new Promise<void>((resolve) => {\n resolve(client.realtime.setAuth(token));\n }).then(ignore, ignore);\n}\n\n/**\n * Subscribes to changes in the signed-in trainee's conversation over\n * Supabase Realtime, and returns a function that ends the subscription.\n *\n * It fetches a token (`messages.realtimeToken`) and the connection settings\n * (`app.bootstrap`), creates a client with `options.realtime`, hands the\n * socket the token, and joins the private topic the token names. The token\n * is renewed a minute before it expires, on the open channel; a renewed\n * token for another topic (another trainee signed in on the same client)\n * leaves that channel and its client and joins the new topic on a new one.\n * A channel Realtime closes is joined afresh on the same client after a\n * wait (5 seconds, 15, then every minute), with the token handed to the\n * socket again first, or a new one if it has expired. Requests that fail\n * for now are retried every minute; a refused token (`auth_required`,\n * `forbidden`, `not_found`, `validation`) ends the subscription with\n * `refused`.\n *\n * `onChange` carries no data: read the conversation and the unread count\n * again. `onCoachTyping`, when given, says who on the coaching team is\n * writing to the trainee while the channel is live\n * ({@link CoachTypingState}). Poll while the status is `connecting` or\n * `fallback`. Needs\n * `@supabase/supabase-js` 2.87.1 or later (see {@link RealtimeClientFactory}).\n *\n * @example\n * ```ts\n * import { createClient } from \"@supabase/supabase-js\";\n * import { MESSAGES_FALLBACK_POLL_MS, subscribeToMessages } from \"@fytkit/sdk\";\n *\n * const unsubscribe = subscribeToMessages(fytkit, {\n * // A client for the subscription alone, carrying the trainee's token only.\n * realtime: ({ url, anonKey }) =>\n * createClient(url, anonKey, {\n * auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n * }),\n * onChange: () => refreshConversation(),\n * onStatus: (status) => setPolling(status === \"connecting\" || status === \"fallback\"),\n * });\n * ```\n */\nexport function subscribeToMessages(\n client: MessagesRealtimeSource,\n options: SubscribeToMessagesOptions,\n): () => void {\n let closed = false;\n let lastStatus: MessagesRealtimeStatus | null = null;\n // The renewal timer: the next token request, or a retry of one.\n let timer: ReturnType<typeof setTimeout> | null = null;\n let renewing = false;\n let connection: RealtimeConnection | null = null;\n // The client the subscription owns, the grant whose token its socket\n // holds, and the channel once joined: one trainee's, replaced together.\n let socket: RealtimeClientLike | null = null;\n let held: MessagesRealtimeGrant | null = null;\n let channel: RealtimeChannelLike | null = null;\n // A fresh join waiting after Realtime closed the channel, and how many\n // closures came one after another without a subscription between.\n let rejoinTimer: ReturnType<typeof setTimeout> | null = null;\n let closures = 0;\n // Stops listening for a sign-out on the client, once the subscription ends.\n let stopAuthListener: (() => void) | null = null;\n // Who is writing, by their key, oldest first, each with the timer that drops them.\n const writing = new Map<\n string,\n { readonly name: string; readonly expiry: ReturnType<typeof setTimeout> }\n >();\n\n // The names last reported, so a change that shows the same names reports nothing.\n let reportedNames: readonly string[] = [];\n\n const reportTyping = (): void => {\n if (closed) return;\n // Two staff members of one name show it once.\n const names = [...new Set([...writing.values()].map((writer) => writer.name))];\n if (\n names.length === reportedNames.length &&\n names.every((name, i) => name === reportedNames[i])\n ) {\n return;\n }\n reportedNames = names;\n options.onCoachTyping?.(names.length === 0 ? { kind: \"idle\" } : { kind: \"typing\", names });\n };\n\n /** Forgets everyone writing (the channel is lost or replaced), and says so unless it ended. */\n const clearTyping = (): void => {\n if (writing.size === 0) return;\n for (const writer of writing.values()) clearTimeout(writer.expiry);\n writing.clear();\n reportTyping();\n };\n\n const onTypingSign = (sign: CoachTypingSign): void => {\n const known = writing.get(sign.key);\n if (known !== undefined) clearTimeout(known.expiry);\n if (sign.state === \"stopped\") {\n if (known === undefined) return;\n writing.delete(sign.key);\n reportTyping();\n return;\n }\n writing.set(sign.key, {\n name: sign.name,\n expiry: setTimeout(() => {\n writing.delete(sign.key);\n reportTyping();\n }, COACH_TYPING_EXPIRY_MS),\n });\n reportTyping();\n };\n\n const report = (status: MessagesRealtimeStatus): void => {\n if (closed || status === lastStatus) return;\n lastStatus = status;\n // Typing is heard only while live: off it, nobody can say they stopped.\n if (status !== \"live\") clearTyping();\n options.onStatus?.(status);\n };\n\n const clearTimer = (): void => {\n if (timer === null) return;\n clearTimeout(timer);\n timer = null;\n };\n\n const after = (delayMs: number, run: () => Promise<void>): void => {\n clearTimer();\n if (closed) return;\n timer = setTimeout(() => {\n timer = null;\n void run();\n }, delayMs);\n };\n\n const cancelRejoin = (): void => {\n if (rejoinTimer !== null) clearTimeout(rejoinTimer);\n rejoinTimer = null;\n };\n\n /** Leaves the channel and drops the socket; nothing they report counts any more. */\n const drop = (): void => {\n const open = socket;\n const joined = channel;\n socket = null;\n held = null;\n channel = null;\n cancelRejoin();\n closures = 0;\n clearTyping();\n if (open === null) return;\n if (joined !== null) Promise.resolve(open.removeChannel(joined)).then(ignore, ignore);\n open.realtime.disconnect();\n };\n\n const close = (): void => {\n if (closed) return;\n closed = true;\n clearTimer();\n drop();\n const stop = stopAuthListener;\n stopAuthListener = null;\n stop?.();\n };\n\n /**\n * Stops, then reports a last status: nothing is asked or reported again.\n * Stopping first means an `onStatus` that throws can't keep the channel.\n */\n const finish = (status: MessagesRealtimeStatus): void => {\n if (closed) return;\n const changed = status !== lastStatus;\n const wasTyping = writing.size > 0;\n lastStatus = status;\n close();\n if (wasTyping) options.onCoachTyping?.({ kind: \"idle\" });\n if (changed) options.onStatus?.(status);\n };\n\n const join = (joinTopic: string, open: RealtimeClientLike): void => {\n // Unsubscribed, the socket was replaced while it took the token, or a\n // join on it has been made since.\n if (closed || socket !== open || channel !== null) return;\n const joined = open.channel(joinTopic, { config: { private: true } });\n channel = joined;\n // A channel the subscription has left (a new trainee's replaced it,\n // Realtime closed it, or it ended) reports nothing.\n const current = (): boolean => !closed && channel === joined;\n joined\n .on(\"broadcast\", { event: THREAD_CHANGED_EVENT }, () => {\n if (current()) options.onChange();\n })\n .on(\"broadcast\", { event: COACH_TYPING_EVENT }, (message) => {\n const sign = coachTypingSign(message);\n if (sign !== null && current()) onTypingSign(sign);\n })\n .subscribe((status) => {\n if (!current()) return;\n if (status === \"SUBSCRIBED\") {\n closures = 0;\n report(\"live\");\n // Changes between the last read and the join were not heard.\n options.onChange();\n return;\n }\n report(\"fallback\");\n // CHANNEL_ERROR or TIMED_OUT: realtime-js joins this channel again\n // by itself. CLOSED (a Realtime restart, a token that expired before\n // its renewal landed): realtime-js has let it go for good, so join a\n // fresh one after a wait.\n if (status === \"CLOSED\") {\n channel = null;\n scheduleRejoin();\n }\n });\n };\n\n /**\n * Hands `open` the grant's token, then joins its topic: a join sent\n * before the socket holds the token carries the anon key alone, which the\n * private topic refuses.\n */\n const authoriseAndJoin = (grant: MessagesRealtimeGrant, open: RealtimeClientLike): void => {\n void authorise(open, grant.token).then(() => join(grant.topic, open));\n };\n\n /** Joins afresh after Realtime closed the channel, with a token it will take. */\n const rejoin = (): void => {\n const open = socket;\n const grant = held;\n if (closed || open === null || grant === null || channel !== null) return;\n // An expired token would be refused: the renewal joins with a new one.\n if (!isUsable(grant.expiresAt, Date.now())) {\n void renew();\n return;\n }\n authoriseAndJoin(grant, open);\n };\n\n const scheduleRejoin = (): void => {\n if (closed || rejoinTimer !== null) return;\n closures += 1;\n rejoinTimer = setTimeout(() => {\n rejoinTimer = null;\n rejoin();\n }, rejoinDelay(closures));\n };\n\n /** Opens a client on the settings and joins the grant's topic once its socket holds the token. */\n const connect = (grant: MessagesRealtimeGrant, settings: RealtimeConnection): void => {\n let opened: RealtimeClientLike;\n try {\n opened = options.realtime(settings);\n } catch {\n // The app's factory can't make a client (a malformed URL, say):\n // asking again won't change that.\n finish(\"fallback\");\n return;\n }\n socket = opened;\n held = grant;\n after(renewalDelay(grant.expiresAt, Date.now()), renew);\n authoriseAndJoin(grant, opened);\n };\n\n /** Opens the subscription on a grant: its topic, the settings (fetched once), a client. */\n const openGrant = async (grant: MessagesRealtimeGrant): Promise<void> => {\n // A server from before `topic` was added: no topic to join.\n if (!isMemberTopic(grant.topic)) {\n finish(\"fallback\");\n return;\n }\n\n if (connection === null) {\n let realtime: unknown;\n try {\n const bootstrap = await client.app.bootstrap();\n realtime = bootstrap.status === \"ok\" ? bootstrap.data.realtime : undefined;\n } catch (error) {\n if (closed) return;\n if (isFinalError(error)) {\n finish(\"fallback\");\n return;\n }\n report(\"fallback\");\n after(RETRY_MS, start);\n return;\n }\n if (closed) return;\n // A server from before the settings were published.\n if (!isConnection(realtime)) {\n finish(\"fallback\");\n return;\n }\n connection = realtime;\n }\n\n connect(grant, connection);\n };\n\n const renew = async (): Promise<void> => {\n // One request at a time: the one in flight joins when it lands.\n if (renewing) return;\n renewing = true;\n let grant: MessagesRealtimeGrant;\n try {\n grant = (await client.messages.realtimeToken({})).data;\n } catch (error) {\n if (closed) return;\n // A refusal stands: the token from before is not used again.\n if (isFinalError(error)) finish(\"refused\");\n else after(RETRY_MS, renew);\n return;\n } finally {\n renewing = false;\n }\n const current = socket;\n if (closed || current === null || held === null) return;\n if (grant.topic !== held.topic) {\n // Another trainee signed in on the same client: the open channel is\n // the previous trainee's. Leave it and open the new trainee's, as the\n // member portal does when its member changes.\n drop();\n report(\"connecting\");\n await openGrant(grant);\n return;\n }\n held = grant;\n after(renewalDelay(grant.expiresAt, Date.now()), renew);\n if (channel !== null) {\n // The open channel carries on with the new token; no rejoin.\n void authorise(current, grant.token);\n return;\n }\n // Realtime closed the channel: join afresh now, with the new token.\n cancelRejoin();\n authoriseAndJoin(grant, current);\n };\n\n const start = async (): Promise<void> => {\n let grant: MessagesRealtimeGrant;\n try {\n grant = (await client.messages.realtimeToken({})).data;\n } catch (error) {\n if (closed) return;\n if (isFinalError(error)) {\n finish(\"refused\");\n return;\n }\n report(\"fallback\");\n after(RETRY_MS, start);\n return;\n }\n if (closed) return;\n await openGrant(grant);\n };\n\n report(\"connecting\");\n // A sign-out leaves no trainee to listen for: stop now, not at the next\n // renewal, so the previous trainee's channel stays open no longer.\n stopAuthListener =\n client.auth?.onAuthStateChange((event) => {\n if (SIGNED_OUT_EVENTS.has(event.type)) finish(\"refused\");\n }) ?? null;\n void start();\n return close;\n}\n","import { createAppNamespace } from \"./app\";\nimport { createAuthNamespace } from \"./auth\";\nimport { DEFAULT_BASE_URL } from \"./base-url\";\nimport { createDataNamespaces } from \"./data\";\nimport { createLocalStorageAdapter, createMemoryStorageAdapter } from \"./storage\";\nimport { attachRefreshInterceptor, createTransport } from \"./transport\";\nimport type { FytkitClient, FytkitClientOptions } from \"./types\";\n\nexport function createFytkitClient(options: FytkitClientOptions): FytkitClient {\n if (!options || typeof options.clientId !== \"string\" || options.clientId.length === 0) {\n throw new Error(\"createFytkitClient: `clientId` is required\");\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const storage = options.storage ?? createLocalStorageAdapter(options.clientId);\n\n // `auth` is declared after `transport` because `createAuthNamespace`\n // needs the transport to make its RPC calls. The transport in turn needs\n // a `getSession` that reads the auth cache — we resolve the cycle with a\n // late-bound reference: the closure below reads `auth` lazily, which is\n // safe because no request can fire before construction finishes.\n let auth: ReturnType<typeof createAuthNamespace> | null = null;\n\n const transport = createTransport({\n baseUrl,\n clientId: options.clientId,\n getSession: () => auth?.getSession() ?? null,\n fetch: options.fetch,\n });\n\n const authInstance = createAuthNamespace({ transport, storage });\n auth = authInstance;\n attachRefreshInterceptor({ transport, auth: authInstance });\n\n const data = createDataNamespaces(transport);\n\n return {\n clientId: options.clientId,\n auth: {\n signUp: authInstance.signUp,\n signIn: authInstance.signIn,\n signOut: authInstance.signOut,\n getSession: authInstance.getSession,\n onAuthStateChange: authInstance.onAuthStateChange,\n requestPasswordReset: authInstance.requestPasswordReset,\n confirmPasswordReset: authInstance.confirmPasswordReset,\n confirmEmail: authInstance.confirmEmail,\n resendVerification: authInstance.resendVerification,\n verifyTotp: authInstance.verifyTotp,\n verifyBackupCode: authInstance.verifyBackupCode,\n requestEmailOtp: authInstance.requestEmailOtp,\n verifyEmailOtp: authInstance.verifyEmailOtp,\n getSecurityStatus: authInstance.getSecurityStatus,\n enableTotp: authInstance.enableTotp,\n verifyTotpSetup: authInstance.verifyTotpSetup,\n disableTwoFactor: authInstance.disableTwoFactor,\n regenerateBackupCodes: authInstance.regenerateBackupCodes,\n changePassword: authInstance.changePassword,\n changeEmail: authInstance.changeEmail,\n confirmEmailChange: authInstance.confirmEmailChange,\n listSessions: authInstance.listSessions,\n revokeSession: authInstance.revokeSession,\n revokeOtherSessions: authInstance.revokeOtherSessions,\n closeAccount: authInstance.closeAccount,\n },\n ...data,\n app: createAppNamespace(transport),\n };\n}\n\n// Public re-exports.\nexport { createLocalStorageAdapter, createMemoryStorageAdapter };\n// `PublicApi` is the inferred router type that backs every namespace\n// on `FytkitClient` (e.g. `client.workouts` is `PublicApi[\"workout\"]`).\n// Re-exported so consumers can write generics over the router shape —\n// previously this surfaced as an `ae-forgotten-export` warning on\n// `FytkitClient` in the api-extractor report. The api-extractor will\n// still warn about `PublicRouter`/`publicRouter` being unexported —\n// that is intentional, those are implementation-detail names from the\n// generated snapshot that should not be part of the consumer surface.\nexport type { PublicApi } from \"@fytkit/types\";\nexport type {\n AppBootstrapData,\n AppBootstrapOptions,\n AppBootstrapResult,\n AppNamespace,\n} from \"./app\";\nexport type {\n JoinCodeCoach,\n JoinCodeResolution,\n JoinCodeTheme,\n ResolveJoinCodeOptions,\n} from \"./join\";\nexport { resolveJoinCode } from \"./join\";\nexport type {\n PaymentMode,\n PaymentPlatform,\n PaymentPolicy,\n ResolvePaymentModeOptions,\n} from \"./payment-policy\";\nexport { resolvePaymentMode } from \"./payment-policy\";\nexport type {\n CoachTypingState,\n MessagesRealtimeAuthEvent,\n MessagesRealtimeGrant,\n MessagesRealtimeSource,\n MessagesRealtimeStatus,\n RealtimeChannelLike,\n RealtimeClientFactory,\n RealtimeClientLike,\n RealtimeConnection,\n SubscribeToMessagesOptions,\n} from \"./realtime\";\nexport {\n COACH_TYPING_EXPIRY_MS,\n MESSAGES_FALLBACK_POLL_MS,\n subscribeToMessages,\n} from \"./realtime\";\nexport type {\n FytkitRefusal,\n FytkitRefusalDataByReason,\n FytkitRefusalReason,\n FytkitRefusalRetryAt,\n} from \"./refusal\";\nexport { refusalData } from \"./refusal\";\nexport type {\n AuthState,\n FytkitClient,\n FytkitClientOptions,\n FytkitError,\n FytkitErrorCode,\n ResendVerificationResult,\n SecurityStatus,\n Session,\n SessionInfo,\n SignInResult,\n TokenStorage,\n TotpEnrollment,\n TwoFactorChallenge,\n TwoFactorMethod,\n TwoFactorToken,\n} from \"./types\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fytkit/sdk",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Vanilla TypeScript SDK for fytkit's public API",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",