@fytkit/sdk 3.5.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 +39 -0
- package/README.md +71 -6
- package/dist/index.d.ts +2576 -942
- package/dist/index.js +179 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ORPCError, isORPCErrorJson, createORPCErrorFromJson, createORPCClient } from '@orpc/client';
|
|
2
2
|
import { RPCLink } from '@orpc/client/fetch';
|
|
3
3
|
|
|
4
4
|
// src/error.ts
|
|
@@ -20,7 +20,8 @@ function pickRefusal(data) {
|
|
|
20
20
|
if (!isRecord(data)) return {};
|
|
21
21
|
const { reason, ...rest } = data;
|
|
22
22
|
if (typeof reason === "string" && reason.length > 0) return { refusal: { reason, data: rest } };
|
|
23
|
-
if (data.quotaCode === MEMBER_CAP_QUOTA)
|
|
23
|
+
if (data.quotaCode === MEMBER_CAP_QUOTA)
|
|
24
|
+
return { refusal: { reason: MEMBER_CAP_QUOTA, data: {} } };
|
|
24
25
|
return {};
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -95,6 +96,28 @@ function normalizeError(err) {
|
|
|
95
96
|
return { kind: "server", status: 0 };
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
// src/app.ts
|
|
100
|
+
var UNEXPECTED_NOT_MODIFIED = { kind: "server", status: 304 };
|
|
101
|
+
function createAppNamespace(transport) {
|
|
102
|
+
const bootstrap = async (options = {}) => {
|
|
103
|
+
const etag = options.etag ?? null;
|
|
104
|
+
let output;
|
|
105
|
+
try {
|
|
106
|
+
output = await transport.client.app.bootstrap(void 0, {
|
|
107
|
+
context: etag === null ? {} : { ifNoneMatch: etag }
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw normalizeError(error);
|
|
111
|
+
}
|
|
112
|
+
if (output === void 0 || output === null) {
|
|
113
|
+
if (etag === null) throw UNEXPECTED_NOT_MODIFIED;
|
|
114
|
+
return { status: "unchanged", etag };
|
|
115
|
+
}
|
|
116
|
+
return { status: "ok", data: output.data, etag: output.meta.etag };
|
|
117
|
+
};
|
|
118
|
+
return { bootstrap };
|
|
119
|
+
}
|
|
120
|
+
|
|
98
121
|
// src/events.ts
|
|
99
122
|
function createEventBus() {
|
|
100
123
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -235,6 +258,10 @@ function createAuthNamespace(opts) {
|
|
|
235
258
|
const confirmEmail = async (input) => {
|
|
236
259
|
await call(() => opts.transport.client.auth.confirmEmail(input));
|
|
237
260
|
};
|
|
261
|
+
const resendVerification = async () => {
|
|
262
|
+
const result = await call(() => opts.transport.client.auth.resendVerification({}));
|
|
263
|
+
return result.data;
|
|
264
|
+
};
|
|
238
265
|
const verifyTotp = async (input) => {
|
|
239
266
|
const result = await call(
|
|
240
267
|
() => opts.transport.client.auth.verifyTotp({ token: input.challenge.token, code: input.code })
|
|
@@ -289,7 +316,8 @@ function createAuthNamespace(opts) {
|
|
|
289
316
|
adoptRotatedToken(result.data.accessToken);
|
|
290
317
|
};
|
|
291
318
|
const disableTwoFactor = async (input) => {
|
|
292
|
-
await call(() => opts.transport.client.auth.disableTwoFactor(input));
|
|
319
|
+
const result = await call(() => opts.transport.client.auth.disableTwoFactor(input));
|
|
320
|
+
adoptRotatedToken(result.data.accessToken);
|
|
293
321
|
};
|
|
294
322
|
const regenerateBackupCodes = async (input) => {
|
|
295
323
|
const result = await call(() => opts.transport.client.auth.regenerateBackupCodes(input));
|
|
@@ -308,6 +336,9 @@ function createAuthNamespace(opts) {
|
|
|
308
336
|
const changeEmail = async (input) => {
|
|
309
337
|
await call(() => opts.transport.client.auth.changeEmail(input));
|
|
310
338
|
};
|
|
339
|
+
const confirmEmailChange = async (input) => {
|
|
340
|
+
await call(() => opts.transport.client.auth.confirmEmailChange(input));
|
|
341
|
+
};
|
|
311
342
|
const listSessions = async () => {
|
|
312
343
|
const result = await call(() => opts.transport.client.auth.listSessions());
|
|
313
344
|
return result.data;
|
|
@@ -364,6 +395,7 @@ function createAuthNamespace(opts) {
|
|
|
364
395
|
requestPasswordReset,
|
|
365
396
|
confirmPasswordReset,
|
|
366
397
|
confirmEmail,
|
|
398
|
+
resendVerification,
|
|
367
399
|
verifyTotp,
|
|
368
400
|
verifyBackupCode,
|
|
369
401
|
requestEmailOtp,
|
|
@@ -375,6 +407,7 @@ function createAuthNamespace(opts) {
|
|
|
375
407
|
regenerateBackupCodes,
|
|
376
408
|
changePassword,
|
|
377
409
|
changeEmail,
|
|
410
|
+
confirmEmailChange,
|
|
378
411
|
listSessions,
|
|
379
412
|
revokeSession,
|
|
380
413
|
revokeOtherSessions,
|
|
@@ -384,6 +417,9 @@ function createAuthNamespace(opts) {
|
|
|
384
417
|
};
|
|
385
418
|
}
|
|
386
419
|
|
|
420
|
+
// src/base-url.ts
|
|
421
|
+
var DEFAULT_BASE_URL = "https://api.fytkit.com";
|
|
422
|
+
|
|
387
423
|
// src/data.ts
|
|
388
424
|
function wrapSlice(slice) {
|
|
389
425
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -422,11 +458,15 @@ function createDataNamespaces(transport) {
|
|
|
422
458
|
sessionLogs: wrapSlice(c.sessionLog),
|
|
423
459
|
progression: wrapSlice(c.progression),
|
|
424
460
|
bodyMetrics: wrapSlice(c.bodyMetric),
|
|
461
|
+
bodyMeasurements: wrapSlice(c.bodyMeasurement),
|
|
462
|
+
progressPhotos: wrapSlice(c.progressPhoto),
|
|
425
463
|
checkIns: wrapSlice(c.checkin),
|
|
426
464
|
messages: wrapSlice(c.message),
|
|
465
|
+
timeline: wrapSlice(c.timeline),
|
|
466
|
+
health: wrapSlice(c.health),
|
|
467
|
+
habits: wrapSlice(c.habit),
|
|
427
468
|
progress: wrapSlice(c.progress),
|
|
428
469
|
media: wrapSlice(c.media),
|
|
429
|
-
appConfig: wrapSlice(c.appConfig),
|
|
430
470
|
subscription: wrapSlice(c.subscription),
|
|
431
471
|
profile: wrapSlice(c.profile),
|
|
432
472
|
nutritionLog: wrapSlice(c.nutritionLog),
|
|
@@ -548,7 +588,7 @@ function createTransport(options) {
|
|
|
548
588
|
};
|
|
549
589
|
const link = new RPCLink({
|
|
550
590
|
url: () => `${options.baseUrl}/rpc`,
|
|
551
|
-
headers: () => {
|
|
591
|
+
headers: ({ context }) => {
|
|
552
592
|
const session = options.getSession();
|
|
553
593
|
const headers = {
|
|
554
594
|
"X-Fytkit-Client-Id": options.clientId
|
|
@@ -556,6 +596,9 @@ function createTransport(options) {
|
|
|
556
596
|
if (session) {
|
|
557
597
|
headers.Authorization = `Bearer ${session.accessToken}`;
|
|
558
598
|
}
|
|
599
|
+
if (context.ifNoneMatch !== void 0) {
|
|
600
|
+
headers["If-None-Match"] = context.ifNoneMatch;
|
|
601
|
+
}
|
|
559
602
|
return headers;
|
|
560
603
|
},
|
|
561
604
|
fetch: wrappedFetch
|
|
@@ -575,6 +618,7 @@ var CREDENTIAL_PROCEDURES = [
|
|
|
575
618
|
"auth/requestPasswordReset",
|
|
576
619
|
"auth/confirmPasswordReset",
|
|
577
620
|
"auth/confirmEmail",
|
|
621
|
+
"auth/confirmEmailChange",
|
|
578
622
|
"auth/verifyTotp",
|
|
579
623
|
"auth/verifyBackupCode",
|
|
580
624
|
"auth/requestEmailOtp",
|
|
@@ -642,13 +686,82 @@ function attachRefreshInterceptor(opts) {
|
|
|
642
686
|
return retried;
|
|
643
687
|
});
|
|
644
688
|
}
|
|
689
|
+
var INACTIVE = {
|
|
690
|
+
kind: "not_found",
|
|
691
|
+
refusal: { reason: "join_code_inactive", data: {} }
|
|
692
|
+
};
|
|
693
|
+
var IGNORED_SEPARATORS = /[\s\-\u2010-\u2015\u2212]/g;
|
|
694
|
+
var PATH_SAFE_CODE = /^[A-Za-z0-9]{1,64}$/;
|
|
695
|
+
function pathSafeJoinCode(input) {
|
|
696
|
+
const candidate = input.replace(IGNORED_SEPARATORS, "");
|
|
697
|
+
return PATH_SAFE_CODE.test(candidate) ? candidate.toUpperCase() : null;
|
|
698
|
+
}
|
|
699
|
+
async function readJson(response) {
|
|
700
|
+
try {
|
|
701
|
+
return await response.json();
|
|
702
|
+
} catch {
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function refusalOf(response, body) {
|
|
707
|
+
const wait = () => retryAfterHeaderMs(response.headers.get("retry-after"), Date.now()) ?? 0;
|
|
708
|
+
if (isORPCErrorJson(body)) {
|
|
709
|
+
const error = normalizeError(createORPCErrorFromJson(body));
|
|
710
|
+
if (error.kind === "rate_limited" && error.retryAfter === 0) {
|
|
711
|
+
return { ...error, retryAfter: wait() };
|
|
712
|
+
}
|
|
713
|
+
return error;
|
|
714
|
+
}
|
|
715
|
+
if (response.status === 429) return { kind: "rate_limited", retryAfter: wait() };
|
|
716
|
+
return { kind: "server", status: response.status };
|
|
717
|
+
}
|
|
718
|
+
async function resolveJoinCode(code, options = {}) {
|
|
719
|
+
const canonical = pathSafeJoinCode(code);
|
|
720
|
+
if (canonical === null) throw INACTIVE;
|
|
721
|
+
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
722
|
+
const doFetch = options.fetch ?? fetch.bind(globalThis);
|
|
723
|
+
let response;
|
|
724
|
+
try {
|
|
725
|
+
response = await doFetch(`${baseUrl}/api/v1/join/${canonical}`, {
|
|
726
|
+
method: "GET",
|
|
727
|
+
headers: { Accept: "application/json" }
|
|
728
|
+
});
|
|
729
|
+
} catch (error) {
|
|
730
|
+
throw normalizeError(error);
|
|
731
|
+
}
|
|
732
|
+
const body = await readJson(response);
|
|
733
|
+
if (response.ok) {
|
|
734
|
+
if (isRecord(body) && isRecord(body.data)) {
|
|
735
|
+
return body.data;
|
|
736
|
+
}
|
|
737
|
+
throw { kind: "server", status: response.status };
|
|
738
|
+
}
|
|
739
|
+
throw refusalOf(response, body);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/payment-policy.ts
|
|
743
|
+
var PLAN_INFO = { mode: "plan_info" };
|
|
744
|
+
function resolvePaymentMode(policy, options) {
|
|
745
|
+
if (!policy || !Array.isArray(policy.linkOut)) return PLAN_INFO;
|
|
746
|
+
const storefront = options.storefront?.trim().toUpperCase() ?? "";
|
|
747
|
+
if (storefront === "") return PLAN_INFO;
|
|
748
|
+
const rule = policy.linkOut.find(
|
|
749
|
+
(entry) => entry?.platform === options.platform && typeof entry.storefront === "string" && entry.storefront.toUpperCase() === storefront
|
|
750
|
+
);
|
|
751
|
+
if (!rule || !Array.isArray(rule.conditions)) return PLAN_INFO;
|
|
752
|
+
const met = new Set(options.conditionsMet ?? []);
|
|
753
|
+
if (!rule.conditions.every((condition) => met.has(condition))) return PLAN_INFO;
|
|
754
|
+
return { mode: "link_out", conditions: rule.conditions };
|
|
755
|
+
}
|
|
645
756
|
|
|
646
757
|
// src/realtime.ts
|
|
758
|
+
var COACH_TYPING_EXPIRY_MS = 8e3;
|
|
647
759
|
var MESSAGES_FALLBACK_POLL_MS = {
|
|
648
760
|
conversation: 1e4,
|
|
649
761
|
unreadCount: 6e4
|
|
650
762
|
};
|
|
651
763
|
var THREAD_CHANGED_EVENT = "thread_changed";
|
|
764
|
+
var COACH_TYPING_EVENT = "coach_typing";
|
|
652
765
|
var MEMBER_TOPIC_PREFIX = "member:";
|
|
653
766
|
var TOKEN_RENEW_BEFORE_MS = 6e4;
|
|
654
767
|
var TOKEN_MIN_DELAY_MS = 5e3;
|
|
@@ -672,6 +785,17 @@ function isFinalError(error) {
|
|
|
672
785
|
function isMemberTopic(topic) {
|
|
673
786
|
return typeof topic === "string" && topic.startsWith(MEMBER_TOPIC_PREFIX) && topic.length > MEMBER_TOPIC_PREFIX.length;
|
|
674
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
|
+
}
|
|
675
799
|
function isConnection(value) {
|
|
676
800
|
if (typeof value !== "object" || value === null) return false;
|
|
677
801
|
if (!("url" in value) || !("anonKey" in value)) return false;
|
|
@@ -708,9 +832,45 @@ function subscribeToMessages(client, options) {
|
|
|
708
832
|
let rejoinTimer = null;
|
|
709
833
|
let closures = 0;
|
|
710
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
|
+
};
|
|
711
870
|
const report = (status) => {
|
|
712
871
|
if (closed || status === lastStatus) return;
|
|
713
872
|
lastStatus = status;
|
|
873
|
+
if (status !== "live") clearTyping();
|
|
714
874
|
options.onStatus?.(status);
|
|
715
875
|
};
|
|
716
876
|
const clearTimer = () => {
|
|
@@ -738,6 +898,7 @@ function subscribeToMessages(client, options) {
|
|
|
738
898
|
channel = null;
|
|
739
899
|
cancelRejoin();
|
|
740
900
|
closures = 0;
|
|
901
|
+
clearTyping();
|
|
741
902
|
if (open === null) return;
|
|
742
903
|
if (joined !== null) Promise.resolve(open.removeChannel(joined)).then(ignore, ignore);
|
|
743
904
|
open.realtime.disconnect();
|
|
@@ -754,8 +915,10 @@ function subscribeToMessages(client, options) {
|
|
|
754
915
|
const finish = (status) => {
|
|
755
916
|
if (closed) return;
|
|
756
917
|
const changed = status !== lastStatus;
|
|
918
|
+
const wasTyping = writing.size > 0;
|
|
757
919
|
lastStatus = status;
|
|
758
920
|
close();
|
|
921
|
+
if (wasTyping) options.onCoachTyping?.({ kind: "idle" });
|
|
759
922
|
if (changed) options.onStatus?.(status);
|
|
760
923
|
};
|
|
761
924
|
const join = (joinTopic, open) => {
|
|
@@ -765,6 +928,9 @@ function subscribeToMessages(client, options) {
|
|
|
765
928
|
const current = () => !closed && channel === joined;
|
|
766
929
|
joined.on("broadcast", { event: THREAD_CHANGED_EVENT }, () => {
|
|
767
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);
|
|
768
934
|
}).subscribe((status) => {
|
|
769
935
|
if (!current()) return;
|
|
770
936
|
if (status === "SUBSCRIBED") {
|
|
@@ -822,7 +988,8 @@ function subscribeToMessages(client, options) {
|
|
|
822
988
|
if (connection === null) {
|
|
823
989
|
let realtime;
|
|
824
990
|
try {
|
|
825
|
-
|
|
991
|
+
const bootstrap = await client.app.bootstrap();
|
|
992
|
+
realtime = bootstrap.status === "ok" ? bootstrap.data.realtime : void 0;
|
|
826
993
|
} catch (error) {
|
|
827
994
|
if (closed) return;
|
|
828
995
|
if (isFinalError(error)) {
|
|
@@ -899,7 +1066,6 @@ function subscribeToMessages(client, options) {
|
|
|
899
1066
|
}
|
|
900
1067
|
|
|
901
1068
|
// src/index.ts
|
|
902
|
-
var DEFAULT_BASE_URL = "https://api.fytkit.com";
|
|
903
1069
|
function createFytkitClient(options) {
|
|
904
1070
|
if (!options || typeof options.clientId !== "string" || options.clientId.length === 0) {
|
|
905
1071
|
throw new Error("createFytkitClient: `clientId` is required");
|
|
@@ -918,6 +1084,7 @@ function createFytkitClient(options) {
|
|
|
918
1084
|
attachRefreshInterceptor({ transport, auth: authInstance });
|
|
919
1085
|
const data = createDataNamespaces(transport);
|
|
920
1086
|
return {
|
|
1087
|
+
clientId: options.clientId,
|
|
921
1088
|
auth: {
|
|
922
1089
|
signUp: authInstance.signUp,
|
|
923
1090
|
signIn: authInstance.signIn,
|
|
@@ -927,6 +1094,7 @@ function createFytkitClient(options) {
|
|
|
927
1094
|
requestPasswordReset: authInstance.requestPasswordReset,
|
|
928
1095
|
confirmPasswordReset: authInstance.confirmPasswordReset,
|
|
929
1096
|
confirmEmail: authInstance.confirmEmail,
|
|
1097
|
+
resendVerification: authInstance.resendVerification,
|
|
930
1098
|
verifyTotp: authInstance.verifyTotp,
|
|
931
1099
|
verifyBackupCode: authInstance.verifyBackupCode,
|
|
932
1100
|
requestEmailOtp: authInstance.requestEmailOtp,
|
|
@@ -938,15 +1106,17 @@ function createFytkitClient(options) {
|
|
|
938
1106
|
regenerateBackupCodes: authInstance.regenerateBackupCodes,
|
|
939
1107
|
changePassword: authInstance.changePassword,
|
|
940
1108
|
changeEmail: authInstance.changeEmail,
|
|
1109
|
+
confirmEmailChange: authInstance.confirmEmailChange,
|
|
941
1110
|
listSessions: authInstance.listSessions,
|
|
942
1111
|
revokeSession: authInstance.revokeSession,
|
|
943
1112
|
revokeOtherSessions: authInstance.revokeOtherSessions,
|
|
944
1113
|
closeAccount: authInstance.closeAccount
|
|
945
1114
|
},
|
|
946
|
-
...data
|
|
1115
|
+
...data,
|
|
1116
|
+
app: createAppNamespace(transport)
|
|
947
1117
|
};
|
|
948
1118
|
}
|
|
949
1119
|
|
|
950
|
-
export { MESSAGES_FALLBACK_POLL_MS, createFytkitClient, createLocalStorageAdapter, createMemoryStorageAdapter, refusalData, subscribeToMessages };
|
|
1120
|
+
export { COACH_TYPING_EXPIRY_MS, MESSAGES_FALLBACK_POLL_MS, createFytkitClient, createLocalStorageAdapter, createMemoryStorageAdapter, refusalData, resolveJoinCode, resolvePaymentMode, subscribeToMessages };
|
|
951
1121
|
//# sourceMappingURL=index.js.map
|
|
952
1122
|
//# sourceMappingURL=index.js.map
|