@takeal/cusfront-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/customer-CoxPwe5o.d.cts +32 -0
- package/dist/customer-CoxPwe5o.d.ts +32 -0
- package/dist/http-BkZZZI8K.d.cts +72 -0
- package/dist/http-BkZZZI8K.d.ts +72 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +478 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +82 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +93 -0
- package/dist/react/index.d.ts +93 -0
- package/dist/react/index.js +75 -0
- package/dist/react/index.js.map +1 -0
- package/dist/resources/auth.cjs +88 -0
- package/dist/resources/auth.cjs.map +1 -0
- package/dist/resources/auth.d.cts +132 -0
- package/dist/resources/auth.d.ts +132 -0
- package/dist/resources/auth.js +86 -0
- package/dist/resources/auth.js.map +1 -0
- package/dist/resources/balance.cjs +21 -0
- package/dist/resources/balance.cjs.map +1 -0
- package/dist/resources/balance.d.cts +27 -0
- package/dist/resources/balance.d.ts +27 -0
- package/dist/resources/balance.js +19 -0
- package/dist/resources/balance.js.map +1 -0
- package/dist/resources/blog.cjs +39 -0
- package/dist/resources/blog.cjs.map +1 -0
- package/dist/resources/blog.d.cts +112 -0
- package/dist/resources/blog.d.ts +112 -0
- package/dist/resources/blog.js +37 -0
- package/dist/resources/blog.js.map +1 -0
- package/dist/resources/branding.cjs +16 -0
- package/dist/resources/branding.cjs.map +1 -0
- package/dist/resources/branding.d.cts +35 -0
- package/dist/resources/branding.d.ts +35 -0
- package/dist/resources/branding.js +14 -0
- package/dist/resources/branding.js.map +1 -0
- package/dist/resources/cards.cjs +91 -0
- package/dist/resources/cards.cjs.map +1 -0
- package/dist/resources/cards.d.cts +166 -0
- package/dist/resources/cards.d.ts +166 -0
- package/dist/resources/cards.js +89 -0
- package/dist/resources/cards.js.map +1 -0
- package/dist/resources/deposits.cjs +52 -0
- package/dist/resources/deposits.cjs.map +1 -0
- package/dist/resources/deposits.d.cts +168 -0
- package/dist/resources/deposits.d.ts +168 -0
- package/dist/resources/deposits.js +50 -0
- package/dist/resources/deposits.js.map +1 -0
- package/dist/resources/subscriptions.cjs +24 -0
- package/dist/resources/subscriptions.cjs.map +1 -0
- package/dist/resources/subscriptions.d.cts +36 -0
- package/dist/resources/subscriptions.d.ts +36 -0
- package/dist/resources/subscriptions.js +22 -0
- package/dist/resources/subscriptions.js.map +1 -0
- package/dist/telegram.cjs +557 -0
- package/dist/telegram.cjs.map +1 -0
- package/dist/telegram.d.cts +105 -0
- package/dist/telegram.d.ts +105 -0
- package/dist/telegram.js +550 -0
- package/dist/telegram.js.map +1 -0
- package/dist/webhooks.cjs +78 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +70 -0
- package/dist/webhooks.d.ts +70 -0
- package/dist/webhooks.js +72 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +123 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/react/context.ts
|
|
6
|
+
var ClientContext = react.createContext(null);
|
|
7
|
+
ClientContext.displayName = "CusfrontClientContext";
|
|
8
|
+
function ClientProvider(props) {
|
|
9
|
+
return react.createElement(
|
|
10
|
+
ClientContext.Provider,
|
|
11
|
+
{ value: props.client },
|
|
12
|
+
props.children
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function useClient() {
|
|
16
|
+
const client = react.useContext(ClientContext);
|
|
17
|
+
if (client === null) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"@takeal/cusfront-sdk/react: useClient() (or a data hook) was called outside of a <ClientProvider>. Wrap your app in <ClientProvider client={createClient(...)}>."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return client;
|
|
23
|
+
}
|
|
24
|
+
function useAsyncResource(fetcher, deps) {
|
|
25
|
+
const [data, setData] = react.useState(null);
|
|
26
|
+
const [error, setError] = react.useState(null);
|
|
27
|
+
const [loading, setLoading] = react.useState(true);
|
|
28
|
+
const [nonce, setNonce] = react.useState(0);
|
|
29
|
+
const refetch = react.useCallback(async () => {
|
|
30
|
+
setNonce((n) => n + 1);
|
|
31
|
+
}, []);
|
|
32
|
+
react.useEffect(() => {
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
setLoading(true);
|
|
36
|
+
setError(null);
|
|
37
|
+
fetcher(controller.signal).then((result) => {
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
setData(result);
|
|
40
|
+
}).catch((e) => {
|
|
41
|
+
if (cancelled) return;
|
|
42
|
+
if (controller.signal.aborted) return;
|
|
43
|
+
setError(e);
|
|
44
|
+
}).finally(() => {
|
|
45
|
+
if (cancelled) return;
|
|
46
|
+
setLoading(false);
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
cancelled = true;
|
|
50
|
+
controller.abort();
|
|
51
|
+
};
|
|
52
|
+
}, [...deps, nonce]);
|
|
53
|
+
return { data, error, loading, refetch };
|
|
54
|
+
}
|
|
55
|
+
function useMe() {
|
|
56
|
+
const client = useClient();
|
|
57
|
+
return useAsyncResource(() => client.auth.me(), [client]);
|
|
58
|
+
}
|
|
59
|
+
function useDeposits() {
|
|
60
|
+
const client = useClient();
|
|
61
|
+
return useAsyncResource(() => client.deposits.list(), [client]);
|
|
62
|
+
}
|
|
63
|
+
function useCards() {
|
|
64
|
+
const client = useClient();
|
|
65
|
+
return useAsyncResource(() => client.cards.list(), [client]);
|
|
66
|
+
}
|
|
67
|
+
function useBalance(currency) {
|
|
68
|
+
const client = useClient();
|
|
69
|
+
return useAsyncResource(
|
|
70
|
+
() => client.balance.get(currency),
|
|
71
|
+
[client, currency]
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exports.ClientProvider = ClientProvider;
|
|
76
|
+
exports.useBalance = useBalance;
|
|
77
|
+
exports.useCards = useCards;
|
|
78
|
+
exports.useClient = useClient;
|
|
79
|
+
exports.useDeposits = useDeposits;
|
|
80
|
+
exports.useMe = useMe;
|
|
81
|
+
//# sourceMappingURL=index.cjs.map
|
|
82
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/context.ts","../../src/react/hooks.ts"],"names":["createContext","createElement","useContext","useState","useCallback","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, createElement, useContext, type ReactNode } from \"react\";\nimport type { CusfrontClient } from \"../index.js\";\n\n/**\n * React context carrying a single {@link CusfrontClient} instance.\n *\n * The client is created once by the consumer (via `createClient`) and\n * handed to {@link ClientProvider}; every hook in this entry point\n * reads it back through {@link useClient}. Keeping the client in\n * context (rather than module state) means multiple deployments /\n * brands can coexist in one tree, and the SDK ships no global\n * singleton.\n *\n * SSR-safe: nothing here touches `window` / `document`, and the\n * context default is `null` so a missing provider fails loudly\n * instead of silently using a stale client.\n */\nconst ClientContext = createContext<CusfrontClient | null>(null);\nClientContext.displayName = \"CusfrontClientContext\";\n\nexport interface ClientProviderProps {\n /** The client built by `createClient(...)`. Create it once (e.g. in\n * a module-level `const` or a `useMemo`) and pass it here — do not\n * rebuild it on every render. */\n client: CusfrontClient;\n children?: ReactNode;\n}\n\n/**\n * Provides a {@link CusfrontClient} to the subtree. Place it near the\n * root of your app:\n *\n * ```tsx\n * const client = createClient({ baseUrl: \"https://api.example.com\" });\n *\n * <ClientProvider client={client}>\n * <App />\n * </ClientProvider>\n * ```\n *\n * Authored with `createElement` rather than JSX so the SDK's build\n * needs no JSX transform configured.\n */\nexport function ClientProvider(props: ClientProviderProps) {\n return createElement(\n ClientContext.Provider,\n { value: props.client },\n props.children,\n );\n}\n\n/**\n * Read the {@link CusfrontClient} from context. Throws a clear error\n * when used outside a {@link ClientProvider} — surfacing the missing\n * wiring at the call site instead of a confusing null-deref later.\n */\nexport function useClient(): CusfrontClient {\n const client = useContext(ClientContext);\n if (client === null) {\n throw new Error(\n \"@takeal/cusfront-sdk/react: useClient() (or a data hook) was called \" +\n \"outside of a <ClientProvider>. Wrap your app in \" +\n \"<ClientProvider client={createClient(...)}>.\",\n );\n }\n return client;\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport { useClient } from \"./context.js\";\nimport type { User } from \"../resources/auth.js\";\nimport type { Deposit } from \"../resources/deposits.js\";\nimport type { Card } from \"../resources/cards.js\";\nimport type { Balance } from \"../resources/balance.js\";\n\n/**\n * Data-fetching hooks for the React entry point.\n *\n * Each hook wraps one resource call and exposes the same small,\n * predictable shape:\n *\n * ```ts\n * const { data, error, loading, refetch } = useDeposits();\n * ```\n *\n * Design notes:\n * - Pure React (`useState` + `useEffect` + `useCallback`); no external\n * data-fetching library, so the React entry stays tiny and the\n * consumer keeps full control of caching.\n * - The effect is cancellation-guarded: if the component unmounts (or\n * the inputs change) before the request settles, the stale result is\n * dropped instead of writing to an unmounted component.\n * - SSR-safe: the fetch only runs inside `useEffect`, which never\n * executes during server render.\n * - `error` holds whatever the resource threw — typically the SDK's\n * `ApiError` / `NetworkError` (branch with `isApiError` /\n * `isNetworkError`).\n */\n\n/** Common return shape for every data hook. */\nexport interface AsyncState<T> {\n /** The resolved value, or `null` before the first success / on error. */\n data: T | null;\n /** The thrown error, or `null` while loading / on success. */\n error: unknown;\n /** True from mount (and from each `refetch`) until the call settles. */\n loading: boolean;\n /** Re-run the underlying resource call. Returns a promise that\n * resolves once the refetch settles (useful for pull-to-refresh). */\n refetch: () => Promise<void>;\n}\n\n/**\n * Internal engine shared by every public hook.\n *\n * `fetcher` is invoked on mount and whenever the caller's `deps`\n * change. It is handed an `AbortSignal` so resource calls that accept\n * one can cancel in-flight work; results are additionally gated behind\n * a `cancelled` flag so a late resolve never lands after teardown.\n *\n * The caller passes a stable `fetcher` (memoise it with `useCallback`)\n * — `useAsyncResource` does not re-subscribe on identity changes alone,\n * only on the explicit `deps` array, mirroring `useEffect` semantics.\n */\nfunction useAsyncResource<T>(\n fetcher: (signal: AbortSignal) => Promise<T>,\n deps: ReadonlyArray<unknown>,\n): AsyncState<T> {\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [loading, setLoading] = useState<boolean>(true);\n // Bumping this re-runs the effect on demand, reusing the exact same\n // cancellation machinery a dependency change would.\n const [nonce, setNonce] = useState(0);\n\n const refetch = useCallback(async () => {\n setNonce((n) => n + 1);\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n const controller = new AbortController();\n\n setLoading(true);\n setError(null);\n\n fetcher(controller.signal)\n .then((result) => {\n if (cancelled) return;\n setData(result);\n })\n .catch((e: unknown) => {\n if (cancelled) return;\n // Ignore the abort we triggered ourselves on teardown.\n if (controller.signal.aborted) return;\n setError(e);\n })\n .finally(() => {\n if (cancelled) return;\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n controller.abort();\n };\n // `fetcher` is intentionally omitted from the dep list: callers\n // memoise it, and re-runs are driven by `deps` + `nonce` so that an\n // unstable inline fetcher can't cause an effect loop.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [...deps, nonce]);\n\n return { data, error, loading, refetch };\n}\n\n/** Current authenticated user (`client.auth.me()`). 401 surfaces as\n * the thrown `ApiError` in `error` — treat it as \"no session\". */\nexport function useMe(): AsyncState<User> {\n const client = useClient();\n return useAsyncResource<User>(() => client.auth.me(), [client]);\n}\n\n/** The caller's deposits, most recent first (`client.deposits.list()`). */\nexport function useDeposits(): AsyncState<Deposit[]> {\n const client = useClient();\n return useAsyncResource<Deposit[]>(() => client.deposits.list(), [client]);\n}\n\n/** The caller's cards, most recent first (`client.cards.list()`). */\nexport function useCards(): AsyncState<Card[]> {\n const client = useClient();\n return useAsyncResource<Card[]>(() => client.cards.list(), [client]);\n}\n\n/**\n * The user's wallet balance for one currency\n * (`client.balance.get(currency)`). Re-fetches when `currency`\n * changes.\n */\nexport function useBalance(currency: string): AsyncState<Balance> {\n const client = useClient();\n return useAsyncResource<Balance>(\n () => client.balance.get(currency),\n [client, currency],\n );\n}\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { CusfrontClient } from '../index.cjs';
|
|
4
|
+
import { User } from '../resources/auth.cjs';
|
|
5
|
+
import { Deposit } from '../resources/deposits.cjs';
|
|
6
|
+
import { Card } from '../resources/cards.cjs';
|
|
7
|
+
import { Balance } from '../resources/balance.cjs';
|
|
8
|
+
import '../http-BkZZZI8K.cjs';
|
|
9
|
+
import '../resources/branding.cjs';
|
|
10
|
+
import '../resources/blog.cjs';
|
|
11
|
+
import '../resources/subscriptions.cjs';
|
|
12
|
+
import '../customer-CoxPwe5o.cjs';
|
|
13
|
+
|
|
14
|
+
interface ClientProviderProps {
|
|
15
|
+
/** The client built by `createClient(...)`. Create it once (e.g. in
|
|
16
|
+
* a module-level `const` or a `useMemo`) and pass it here — do not
|
|
17
|
+
* rebuild it on every render. */
|
|
18
|
+
client: CusfrontClient;
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Provides a {@link CusfrontClient} to the subtree. Place it near the
|
|
23
|
+
* root of your app:
|
|
24
|
+
*
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const client = createClient({ baseUrl: "https://api.example.com" });
|
|
27
|
+
*
|
|
28
|
+
* <ClientProvider client={client}>
|
|
29
|
+
* <App />
|
|
30
|
+
* </ClientProvider>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Authored with `createElement` rather than JSX so the SDK's build
|
|
34
|
+
* needs no JSX transform configured.
|
|
35
|
+
*/
|
|
36
|
+
declare function ClientProvider(props: ClientProviderProps): react.FunctionComponentElement<react.ProviderProps<CusfrontClient | null>>;
|
|
37
|
+
/**
|
|
38
|
+
* Read the {@link CusfrontClient} from context. Throws a clear error
|
|
39
|
+
* when used outside a {@link ClientProvider} — surfacing the missing
|
|
40
|
+
* wiring at the call site instead of a confusing null-deref later.
|
|
41
|
+
*/
|
|
42
|
+
declare function useClient(): CusfrontClient;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Data-fetching hooks for the React entry point.
|
|
46
|
+
*
|
|
47
|
+
* Each hook wraps one resource call and exposes the same small,
|
|
48
|
+
* predictable shape:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* const { data, error, loading, refetch } = useDeposits();
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Design notes:
|
|
55
|
+
* - Pure React (`useState` + `useEffect` + `useCallback`); no external
|
|
56
|
+
* data-fetching library, so the React entry stays tiny and the
|
|
57
|
+
* consumer keeps full control of caching.
|
|
58
|
+
* - The effect is cancellation-guarded: if the component unmounts (or
|
|
59
|
+
* the inputs change) before the request settles, the stale result is
|
|
60
|
+
* dropped instead of writing to an unmounted component.
|
|
61
|
+
* - SSR-safe: the fetch only runs inside `useEffect`, which never
|
|
62
|
+
* executes during server render.
|
|
63
|
+
* - `error` holds whatever the resource threw — typically the SDK's
|
|
64
|
+
* `ApiError` / `NetworkError` (branch with `isApiError` /
|
|
65
|
+
* `isNetworkError`).
|
|
66
|
+
*/
|
|
67
|
+
/** Common return shape for every data hook. */
|
|
68
|
+
interface AsyncState<T> {
|
|
69
|
+
/** The resolved value, or `null` before the first success / on error. */
|
|
70
|
+
data: T | null;
|
|
71
|
+
/** The thrown error, or `null` while loading / on success. */
|
|
72
|
+
error: unknown;
|
|
73
|
+
/** True from mount (and from each `refetch`) until the call settles. */
|
|
74
|
+
loading: boolean;
|
|
75
|
+
/** Re-run the underlying resource call. Returns a promise that
|
|
76
|
+
* resolves once the refetch settles (useful for pull-to-refresh). */
|
|
77
|
+
refetch: () => Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/** Current authenticated user (`client.auth.me()`). 401 surfaces as
|
|
80
|
+
* the thrown `ApiError` in `error` — treat it as "no session". */
|
|
81
|
+
declare function useMe(): AsyncState<User>;
|
|
82
|
+
/** The caller's deposits, most recent first (`client.deposits.list()`). */
|
|
83
|
+
declare function useDeposits(): AsyncState<Deposit[]>;
|
|
84
|
+
/** The caller's cards, most recent first (`client.cards.list()`). */
|
|
85
|
+
declare function useCards(): AsyncState<Card[]>;
|
|
86
|
+
/**
|
|
87
|
+
* The user's wallet balance for one currency
|
|
88
|
+
* (`client.balance.get(currency)`). Re-fetches when `currency`
|
|
89
|
+
* changes.
|
|
90
|
+
*/
|
|
91
|
+
declare function useBalance(currency: string): AsyncState<Balance>;
|
|
92
|
+
|
|
93
|
+
export { type AsyncState, ClientProvider, type ClientProviderProps, useBalance, useCards, useClient, useDeposits, useMe };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { CusfrontClient } from '../index.js';
|
|
4
|
+
import { User } from '../resources/auth.js';
|
|
5
|
+
import { Deposit } from '../resources/deposits.js';
|
|
6
|
+
import { Card } from '../resources/cards.js';
|
|
7
|
+
import { Balance } from '../resources/balance.js';
|
|
8
|
+
import '../http-BkZZZI8K.js';
|
|
9
|
+
import '../resources/branding.js';
|
|
10
|
+
import '../resources/blog.js';
|
|
11
|
+
import '../resources/subscriptions.js';
|
|
12
|
+
import '../customer-CoxPwe5o.js';
|
|
13
|
+
|
|
14
|
+
interface ClientProviderProps {
|
|
15
|
+
/** The client built by `createClient(...)`. Create it once (e.g. in
|
|
16
|
+
* a module-level `const` or a `useMemo`) and pass it here — do not
|
|
17
|
+
* rebuild it on every render. */
|
|
18
|
+
client: CusfrontClient;
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Provides a {@link CusfrontClient} to the subtree. Place it near the
|
|
23
|
+
* root of your app:
|
|
24
|
+
*
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const client = createClient({ baseUrl: "https://api.example.com" });
|
|
27
|
+
*
|
|
28
|
+
* <ClientProvider client={client}>
|
|
29
|
+
* <App />
|
|
30
|
+
* </ClientProvider>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Authored with `createElement` rather than JSX so the SDK's build
|
|
34
|
+
* needs no JSX transform configured.
|
|
35
|
+
*/
|
|
36
|
+
declare function ClientProvider(props: ClientProviderProps): react.FunctionComponentElement<react.ProviderProps<CusfrontClient | null>>;
|
|
37
|
+
/**
|
|
38
|
+
* Read the {@link CusfrontClient} from context. Throws a clear error
|
|
39
|
+
* when used outside a {@link ClientProvider} — surfacing the missing
|
|
40
|
+
* wiring at the call site instead of a confusing null-deref later.
|
|
41
|
+
*/
|
|
42
|
+
declare function useClient(): CusfrontClient;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Data-fetching hooks for the React entry point.
|
|
46
|
+
*
|
|
47
|
+
* Each hook wraps one resource call and exposes the same small,
|
|
48
|
+
* predictable shape:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* const { data, error, loading, refetch } = useDeposits();
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Design notes:
|
|
55
|
+
* - Pure React (`useState` + `useEffect` + `useCallback`); no external
|
|
56
|
+
* data-fetching library, so the React entry stays tiny and the
|
|
57
|
+
* consumer keeps full control of caching.
|
|
58
|
+
* - The effect is cancellation-guarded: if the component unmounts (or
|
|
59
|
+
* the inputs change) before the request settles, the stale result is
|
|
60
|
+
* dropped instead of writing to an unmounted component.
|
|
61
|
+
* - SSR-safe: the fetch only runs inside `useEffect`, which never
|
|
62
|
+
* executes during server render.
|
|
63
|
+
* - `error` holds whatever the resource threw — typically the SDK's
|
|
64
|
+
* `ApiError` / `NetworkError` (branch with `isApiError` /
|
|
65
|
+
* `isNetworkError`).
|
|
66
|
+
*/
|
|
67
|
+
/** Common return shape for every data hook. */
|
|
68
|
+
interface AsyncState<T> {
|
|
69
|
+
/** The resolved value, or `null` before the first success / on error. */
|
|
70
|
+
data: T | null;
|
|
71
|
+
/** The thrown error, or `null` while loading / on success. */
|
|
72
|
+
error: unknown;
|
|
73
|
+
/** True from mount (and from each `refetch`) until the call settles. */
|
|
74
|
+
loading: boolean;
|
|
75
|
+
/** Re-run the underlying resource call. Returns a promise that
|
|
76
|
+
* resolves once the refetch settles (useful for pull-to-refresh). */
|
|
77
|
+
refetch: () => Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/** Current authenticated user (`client.auth.me()`). 401 surfaces as
|
|
80
|
+
* the thrown `ApiError` in `error` — treat it as "no session". */
|
|
81
|
+
declare function useMe(): AsyncState<User>;
|
|
82
|
+
/** The caller's deposits, most recent first (`client.deposits.list()`). */
|
|
83
|
+
declare function useDeposits(): AsyncState<Deposit[]>;
|
|
84
|
+
/** The caller's cards, most recent first (`client.cards.list()`). */
|
|
85
|
+
declare function useCards(): AsyncState<Card[]>;
|
|
86
|
+
/**
|
|
87
|
+
* The user's wallet balance for one currency
|
|
88
|
+
* (`client.balance.get(currency)`). Re-fetches when `currency`
|
|
89
|
+
* changes.
|
|
90
|
+
*/
|
|
91
|
+
declare function useBalance(currency: string): AsyncState<Balance>;
|
|
92
|
+
|
|
93
|
+
export { type AsyncState, ClientProvider, type ClientProviderProps, useBalance, useCards, useClient, useDeposits, useMe };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createContext, createElement, useContext, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/react/context.ts
|
|
4
|
+
var ClientContext = createContext(null);
|
|
5
|
+
ClientContext.displayName = "CusfrontClientContext";
|
|
6
|
+
function ClientProvider(props) {
|
|
7
|
+
return createElement(
|
|
8
|
+
ClientContext.Provider,
|
|
9
|
+
{ value: props.client },
|
|
10
|
+
props.children
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function useClient() {
|
|
14
|
+
const client = useContext(ClientContext);
|
|
15
|
+
if (client === null) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"@takeal/cusfront-sdk/react: useClient() (or a data hook) was called outside of a <ClientProvider>. Wrap your app in <ClientProvider client={createClient(...)}>."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return client;
|
|
21
|
+
}
|
|
22
|
+
function useAsyncResource(fetcher, deps) {
|
|
23
|
+
const [data, setData] = useState(null);
|
|
24
|
+
const [error, setError] = useState(null);
|
|
25
|
+
const [loading, setLoading] = useState(true);
|
|
26
|
+
const [nonce, setNonce] = useState(0);
|
|
27
|
+
const refetch = useCallback(async () => {
|
|
28
|
+
setNonce((n) => n + 1);
|
|
29
|
+
}, []);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
fetcher(controller.signal).then((result) => {
|
|
36
|
+
if (cancelled) return;
|
|
37
|
+
setData(result);
|
|
38
|
+
}).catch((e) => {
|
|
39
|
+
if (cancelled) return;
|
|
40
|
+
if (controller.signal.aborted) return;
|
|
41
|
+
setError(e);
|
|
42
|
+
}).finally(() => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
setLoading(false);
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
cancelled = true;
|
|
48
|
+
controller.abort();
|
|
49
|
+
};
|
|
50
|
+
}, [...deps, nonce]);
|
|
51
|
+
return { data, error, loading, refetch };
|
|
52
|
+
}
|
|
53
|
+
function useMe() {
|
|
54
|
+
const client = useClient();
|
|
55
|
+
return useAsyncResource(() => client.auth.me(), [client]);
|
|
56
|
+
}
|
|
57
|
+
function useDeposits() {
|
|
58
|
+
const client = useClient();
|
|
59
|
+
return useAsyncResource(() => client.deposits.list(), [client]);
|
|
60
|
+
}
|
|
61
|
+
function useCards() {
|
|
62
|
+
const client = useClient();
|
|
63
|
+
return useAsyncResource(() => client.cards.list(), [client]);
|
|
64
|
+
}
|
|
65
|
+
function useBalance(currency) {
|
|
66
|
+
const client = useClient();
|
|
67
|
+
return useAsyncResource(
|
|
68
|
+
() => client.balance.get(currency),
|
|
69
|
+
[client, currency]
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { ClientProvider, useBalance, useCards, useClient, useDeposits, useMe };
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/context.ts","../../src/react/hooks.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, createElement, useContext, type ReactNode } from \"react\";\nimport type { CusfrontClient } from \"../index.js\";\n\n/**\n * React context carrying a single {@link CusfrontClient} instance.\n *\n * The client is created once by the consumer (via `createClient`) and\n * handed to {@link ClientProvider}; every hook in this entry point\n * reads it back through {@link useClient}. Keeping the client in\n * context (rather than module state) means multiple deployments /\n * brands can coexist in one tree, and the SDK ships no global\n * singleton.\n *\n * SSR-safe: nothing here touches `window` / `document`, and the\n * context default is `null` so a missing provider fails loudly\n * instead of silently using a stale client.\n */\nconst ClientContext = createContext<CusfrontClient | null>(null);\nClientContext.displayName = \"CusfrontClientContext\";\n\nexport interface ClientProviderProps {\n /** The client built by `createClient(...)`. Create it once (e.g. in\n * a module-level `const` or a `useMemo`) and pass it here — do not\n * rebuild it on every render. */\n client: CusfrontClient;\n children?: ReactNode;\n}\n\n/**\n * Provides a {@link CusfrontClient} to the subtree. Place it near the\n * root of your app:\n *\n * ```tsx\n * const client = createClient({ baseUrl: \"https://api.example.com\" });\n *\n * <ClientProvider client={client}>\n * <App />\n * </ClientProvider>\n * ```\n *\n * Authored with `createElement` rather than JSX so the SDK's build\n * needs no JSX transform configured.\n */\nexport function ClientProvider(props: ClientProviderProps) {\n return createElement(\n ClientContext.Provider,\n { value: props.client },\n props.children,\n );\n}\n\n/**\n * Read the {@link CusfrontClient} from context. Throws a clear error\n * when used outside a {@link ClientProvider} — surfacing the missing\n * wiring at the call site instead of a confusing null-deref later.\n */\nexport function useClient(): CusfrontClient {\n const client = useContext(ClientContext);\n if (client === null) {\n throw new Error(\n \"@takeal/cusfront-sdk/react: useClient() (or a data hook) was called \" +\n \"outside of a <ClientProvider>. Wrap your app in \" +\n \"<ClientProvider client={createClient(...)}>.\",\n );\n }\n return client;\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport { useClient } from \"./context.js\";\nimport type { User } from \"../resources/auth.js\";\nimport type { Deposit } from \"../resources/deposits.js\";\nimport type { Card } from \"../resources/cards.js\";\nimport type { Balance } from \"../resources/balance.js\";\n\n/**\n * Data-fetching hooks for the React entry point.\n *\n * Each hook wraps one resource call and exposes the same small,\n * predictable shape:\n *\n * ```ts\n * const { data, error, loading, refetch } = useDeposits();\n * ```\n *\n * Design notes:\n * - Pure React (`useState` + `useEffect` + `useCallback`); no external\n * data-fetching library, so the React entry stays tiny and the\n * consumer keeps full control of caching.\n * - The effect is cancellation-guarded: if the component unmounts (or\n * the inputs change) before the request settles, the stale result is\n * dropped instead of writing to an unmounted component.\n * - SSR-safe: the fetch only runs inside `useEffect`, which never\n * executes during server render.\n * - `error` holds whatever the resource threw — typically the SDK's\n * `ApiError` / `NetworkError` (branch with `isApiError` /\n * `isNetworkError`).\n */\n\n/** Common return shape for every data hook. */\nexport interface AsyncState<T> {\n /** The resolved value, or `null` before the first success / on error. */\n data: T | null;\n /** The thrown error, or `null` while loading / on success. */\n error: unknown;\n /** True from mount (and from each `refetch`) until the call settles. */\n loading: boolean;\n /** Re-run the underlying resource call. Returns a promise that\n * resolves once the refetch settles (useful for pull-to-refresh). */\n refetch: () => Promise<void>;\n}\n\n/**\n * Internal engine shared by every public hook.\n *\n * `fetcher` is invoked on mount and whenever the caller's `deps`\n * change. It is handed an `AbortSignal` so resource calls that accept\n * one can cancel in-flight work; results are additionally gated behind\n * a `cancelled` flag so a late resolve never lands after teardown.\n *\n * The caller passes a stable `fetcher` (memoise it with `useCallback`)\n * — `useAsyncResource` does not re-subscribe on identity changes alone,\n * only on the explicit `deps` array, mirroring `useEffect` semantics.\n */\nfunction useAsyncResource<T>(\n fetcher: (signal: AbortSignal) => Promise<T>,\n deps: ReadonlyArray<unknown>,\n): AsyncState<T> {\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [loading, setLoading] = useState<boolean>(true);\n // Bumping this re-runs the effect on demand, reusing the exact same\n // cancellation machinery a dependency change would.\n const [nonce, setNonce] = useState(0);\n\n const refetch = useCallback(async () => {\n setNonce((n) => n + 1);\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n const controller = new AbortController();\n\n setLoading(true);\n setError(null);\n\n fetcher(controller.signal)\n .then((result) => {\n if (cancelled) return;\n setData(result);\n })\n .catch((e: unknown) => {\n if (cancelled) return;\n // Ignore the abort we triggered ourselves on teardown.\n if (controller.signal.aborted) return;\n setError(e);\n })\n .finally(() => {\n if (cancelled) return;\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n controller.abort();\n };\n // `fetcher` is intentionally omitted from the dep list: callers\n // memoise it, and re-runs are driven by `deps` + `nonce` so that an\n // unstable inline fetcher can't cause an effect loop.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [...deps, nonce]);\n\n return { data, error, loading, refetch };\n}\n\n/** Current authenticated user (`client.auth.me()`). 401 surfaces as\n * the thrown `ApiError` in `error` — treat it as \"no session\". */\nexport function useMe(): AsyncState<User> {\n const client = useClient();\n return useAsyncResource<User>(() => client.auth.me(), [client]);\n}\n\n/** The caller's deposits, most recent first (`client.deposits.list()`). */\nexport function useDeposits(): AsyncState<Deposit[]> {\n const client = useClient();\n return useAsyncResource<Deposit[]>(() => client.deposits.list(), [client]);\n}\n\n/** The caller's cards, most recent first (`client.cards.list()`). */\nexport function useCards(): AsyncState<Card[]> {\n const client = useClient();\n return useAsyncResource<Card[]>(() => client.cards.list(), [client]);\n}\n\n/**\n * The user's wallet balance for one currency\n * (`client.balance.get(currency)`). Re-fetches when `currency`\n * changes.\n */\nexport function useBalance(currency: string): AsyncState<Balance> {\n const client = useClient();\n return useAsyncResource<Balance>(\n () => client.balance.get(currency),\n [client, currency],\n );\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/resources/auth.ts
|
|
4
|
+
var AuthResource = class {
|
|
5
|
+
constructor(http, tokens) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
this.tokens = tokens;
|
|
8
|
+
}
|
|
9
|
+
/** Password-auth entry. May resolve to a JWT, or to a step-up
|
|
10
|
+
* challenge that needs `verifyTotp` / `verifyOtp` next. */
|
|
11
|
+
async login(input) {
|
|
12
|
+
const resp = await this.http.post("/auth/login", {
|
|
13
|
+
body: input,
|
|
14
|
+
skipAuth: true
|
|
15
|
+
});
|
|
16
|
+
if (resp.stage === "jwt") {
|
|
17
|
+
await Promise.resolve(this.tokens.set(resp.access_token));
|
|
18
|
+
}
|
|
19
|
+
return resp;
|
|
20
|
+
}
|
|
21
|
+
/** Verify a TOTP code (or XXXX-XXXX backup code) against a
|
|
22
|
+
* `totp_required` challenge. On success the JWT is stored. */
|
|
23
|
+
async verifyTotp(input) {
|
|
24
|
+
const resp = await this.http.post("/auth/login/totp", {
|
|
25
|
+
body: input,
|
|
26
|
+
skipAuth: true
|
|
27
|
+
});
|
|
28
|
+
await Promise.resolve(this.tokens.set(resp.access_token));
|
|
29
|
+
return resp;
|
|
30
|
+
}
|
|
31
|
+
/** Verify a 6-digit OTP code (Telegram / email) against an
|
|
32
|
+
* `otp_sent` challenge. */
|
|
33
|
+
async verifyOtp(input) {
|
|
34
|
+
const resp = await this.http.post("/auth/login/otp/verify", {
|
|
35
|
+
body: input,
|
|
36
|
+
skipAuth: true
|
|
37
|
+
});
|
|
38
|
+
await Promise.resolve(this.tokens.set(resp.access_token));
|
|
39
|
+
return resp;
|
|
40
|
+
}
|
|
41
|
+
/** Swap the 2FA method mid-flow. Resolves to `otp_sent` for
|
|
42
|
+
* telegram/email; for TOTP it short-circuits back to the challenge. */
|
|
43
|
+
async switchMethod(input) {
|
|
44
|
+
return this.http.post("/auth/login/2fa/switch", {
|
|
45
|
+
body: input,
|
|
46
|
+
skipAuth: true
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Exchange a signed Telegram Mini App `initData` payload for a session
|
|
50
|
+
* JWT. The server validates the initData HMAC against the deployment's bot
|
|
51
|
+
* token, then find-or-creates the end-user. First-time Telegram users are
|
|
52
|
+
* auto-provisioned with `user.email_pending === true` — prompt them for a
|
|
53
|
+
* real email and call {@link linkEmail}. Stores the JWT on success.
|
|
54
|
+
*
|
|
55
|
+
* Most consumers use the higher-level `fromInitData` / `fromTelegramWebApp`
|
|
56
|
+
* helpers in `@takeal/cusfront-sdk/telegram`; reach for this directly when
|
|
57
|
+
* you already hold a configured client. */
|
|
58
|
+
async exchangeTelegram(input) {
|
|
59
|
+
const resp = await this.http.post("/auth/telegram/exchange", {
|
|
60
|
+
body: { init_data: input.initData },
|
|
61
|
+
skipAuth: true
|
|
62
|
+
});
|
|
63
|
+
await Promise.resolve(this.tokens.set(resp.access_token));
|
|
64
|
+
return resp;
|
|
65
|
+
}
|
|
66
|
+
/** Attach a real email to the current user (and clear `email_pending`).
|
|
67
|
+
* Used after a Telegram exchange to satisfy the email requirement; also
|
|
68
|
+
* works for a password user changing their address. 409 if taken. */
|
|
69
|
+
async linkEmail(input) {
|
|
70
|
+
return this.http.post("/me/email", { body: { email: input.email } });
|
|
71
|
+
}
|
|
72
|
+
/** Current user — fails 401 when the stored JWT is expired or
|
|
73
|
+
* missing. Useful as a "do I have a session" probe on app boot. */
|
|
74
|
+
async me() {
|
|
75
|
+
return this.http.get("/auth/me");
|
|
76
|
+
}
|
|
77
|
+
/** Local sign-out. The SDK doesn't currently call a server-side
|
|
78
|
+
* endpoint (the Takeal API issues stateless JWTs); clearing the
|
|
79
|
+
* store is enough. Server-side session invalidation may land
|
|
80
|
+
* with a future session table. */
|
|
81
|
+
async signOut() {
|
|
82
|
+
await Promise.resolve(this.tokens.clear());
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
exports.AuthResource = AuthResource;
|
|
87
|
+
//# sourceMappingURL=auth.cjs.map
|
|
88
|
+
//# sourceMappingURL=auth.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/resources/auth.ts"],"names":[],"mappings":";;;AAoGO,IAAM,eAAN,MAAmB;AAAA,EACxB,WAAA,CACmB,MACA,MAAA,EACjB;AAFiB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA,EAIH,MAAM,MAAM,KAAA,EAA2C;AACrD,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,CAAK,KAAoB,aAAA,EAAe;AAAA,MAC9D,IAAA,EAAM,KAAA;AAAA,MACN,QAAA,EAAU;AAAA,KACX,CAAA;AACD,IAAA,IAAI,IAAA,CAAK,UAAU,KAAA,EAAO;AACxB,MAAA,MAAM,QAAQ,OAAA,CAAQ,IAAA,CAAK,OAAO,GAAA,CAAI,IAAA,CAAK,YAAY,CAAC,CAAA;AAAA,IAC1D;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,MAAM,WAAW,KAAA,EAA4C;AAC3D,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,CAAK,KAAgB,kBAAA,EAAoB;AAAA,MAC/D,IAAA,EAAM,KAAA;AAAA,MACN,QAAA,EAAU;AAAA,KACX,CAAA;AACD,IAAA,MAAM,QAAQ,OAAA,CAAQ,IAAA,CAAK,OAAO,GAAA,CAAI,IAAA,CAAK,YAAY,CAAC,CAAA;AACxD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,MAAM,UAAU,KAAA,EAA2C;AACzD,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,CAAK,KAAgB,wBAAA,EAA0B;AAAA,MACrE,IAAA,EAAM,KAAA;AAAA,MACN,QAAA,EAAU;AAAA,KACX,CAAA;AACD,IAAA,MAAM,QAAQ,OAAA,CAAQ,IAAA,CAAK,OAAO,GAAA,CAAI,IAAA,CAAK,YAAY,CAAC,CAAA;AACxD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,MAAM,aAAa,KAAA,EAAoD;AACrE,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAsB,wBAAA,EAA0B;AAAA,MAC/D,IAAA,EAAM,KAAA;AAAA,MACN,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,iBAAiB,KAAA,EAAiD;AACtE,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,CAAK,KAAgB,yBAAA,EAA2B;AAAA,MACtE,IAAA,EAAM,EAAE,SAAA,EAAW,KAAA,CAAM,QAAA,EAAS;AAAA,MAClC,QAAA,EAAU;AAAA,KACX,CAAA;AACD,IAAA,MAAM,QAAQ,OAAA,CAAQ,IAAA,CAAK,OAAO,GAAA,CAAI,IAAA,CAAK,YAAY,CAAC,CAAA;AACxD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,KAAA,EAAyC;AACvD,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAW,WAAA,EAAa,EAAE,IAAA,EAAM,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,EAAM,EAAG,CAAA;AAAA,EAC3E;AAAA;AAAA;AAAA,EAIA,MAAM,EAAA,GAAoB;AACxB,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,GAAA,CAAU,UAAU,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAA,GAAyB;AAC7B,IAAA,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA;AAAA,EAC3C;AACF","file":"auth.cjs","sourcesContent":["import type { HttpClient } from \"../http.js\";\nimport type { TokenStore } from \"../token-store.js\";\n\n/**\n * Auth resource — `client.auth.*`.\n *\n * Covers the end-user-facing staged auth flow: password login\n * with optional TOTP/OTP step-up. The shape mirrors the responses\n * the Takeal API returns from `/auth/login`, `/auth/login/totp`,\n * `/auth/login/otp/verify`, `/auth/login/2fa/switch`, and `/auth/me`.\n *\n * The login methods stash the JWT in the configured `TokenStore` on\n * success so subsequent `client.*` calls authenticate automatically.\n * Step-up responses (TotpRequired / OtpSent) carry their own short-\n * lived challenge token which the caller passes to the next method\n * — those tokens DO NOT go through the TokenStore (they're not the\n * end-state JWT).\n */\n\nexport interface User {\n id: string;\n email: string;\n role: string;\n is_active: boolean;\n /** True for users auto-provisioned via the Telegram Mini App who still hold\n * a synthetic placeholder email. The client should prompt for a real email\n * and attach it via `auth.linkEmail` — this flips the flag to false. */\n email_pending?: boolean;\n created_at: string;\n updated_at: string;\n}\n\nexport interface LoginInput {\n email: string;\n password: string;\n}\n\n/** Discriminated union — `stage` is the discriminator. */\nexport type LoginResponse = JwtIssued | TotpRequired | TotpSetupRequired;\n\nexport interface JwtIssued {\n stage: \"jwt\";\n access_token: string;\n expires_at: string;\n user: User;\n /** True iff the user's role grants `system.view_hub`.\n * Cusfront ignores this; the operator console uses it to gate `/dashboard`. */\n hub_access?: boolean;\n}\n\nexport interface TotpRequired {\n stage: \"totp_required\";\n challenge_token: string;\n expires_at: string;\n /** Alternate second factors. Pick one with `/2fa/switch`. */\n available_methods: AvailableMethod[];\n}\n\nexport interface TotpSetupRequired {\n stage: \"totp_setup_required\";\n challenge_token: string;\n expires_at: string;\n /** OTPAuth URI rendered as a QR by the consumer. */\n otpauth_url: string;\n /** Same secret base32-encoded — for manual entry alongside QR. */\n secret_base32: string;\n}\n\nexport interface AvailableMethod {\n kind: \"totp\" | \"telegram_otp\" | \"email_otp\";\n /** Short user-facing hint, e.g. `\"Telegram (@user)\"` or `\"e****@example.com\"`. */\n hint?: string;\n}\n\nexport interface TotpVerifyInput {\n challenge_token: string;\n /** 6-digit TOTP code OR `XXXX-XXXX` backup code. */\n code: string;\n}\n\nexport interface OtpVerifyInput {\n challenge_token: string;\n /** 6-digit one-time code delivered via the selected channel. */\n code: string;\n}\n\nexport interface SwitchMethodInput {\n challenge_token: string;\n method: AvailableMethod[\"kind\"];\n}\n\nexport interface OtpSentResponse {\n stage: \"otp_sent\";\n challenge_token: string;\n expires_at: string;\n method: AvailableMethod[\"kind\"];\n hint?: string;\n available_methods: AvailableMethod[];\n}\n\nexport class AuthResource {\n constructor(\n private readonly http: HttpClient,\n private readonly tokens: TokenStore,\n ) {}\n\n /** Password-auth entry. May resolve to a JWT, or to a step-up\n * challenge that needs `verifyTotp` / `verifyOtp` next. */\n async login(input: LoginInput): Promise<LoginResponse> {\n const resp = await this.http.post<LoginResponse>(\"/auth/login\", {\n body: input,\n skipAuth: true,\n });\n if (resp.stage === \"jwt\") {\n await Promise.resolve(this.tokens.set(resp.access_token));\n }\n return resp;\n }\n\n /** Verify a TOTP code (or XXXX-XXXX backup code) against a\n * `totp_required` challenge. On success the JWT is stored. */\n async verifyTotp(input: TotpVerifyInput): Promise<JwtIssued> {\n const resp = await this.http.post<JwtIssued>(\"/auth/login/totp\", {\n body: input,\n skipAuth: true,\n });\n await Promise.resolve(this.tokens.set(resp.access_token));\n return resp;\n }\n\n /** Verify a 6-digit OTP code (Telegram / email) against an\n * `otp_sent` challenge. */\n async verifyOtp(input: OtpVerifyInput): Promise<JwtIssued> {\n const resp = await this.http.post<JwtIssued>(\"/auth/login/otp/verify\", {\n body: input,\n skipAuth: true,\n });\n await Promise.resolve(this.tokens.set(resp.access_token));\n return resp;\n }\n\n /** Swap the 2FA method mid-flow. Resolves to `otp_sent` for\n * telegram/email; for TOTP it short-circuits back to the challenge. */\n async switchMethod(input: SwitchMethodInput): Promise<OtpSentResponse> {\n return this.http.post<OtpSentResponse>(\"/auth/login/2fa/switch\", {\n body: input,\n skipAuth: true,\n });\n }\n\n /** Exchange a signed Telegram Mini App `initData` payload for a session\n * JWT. The server validates the initData HMAC against the deployment's bot\n * token, then find-or-creates the end-user. First-time Telegram users are\n * auto-provisioned with `user.email_pending === true` — prompt them for a\n * real email and call {@link linkEmail}. Stores the JWT on success.\n *\n * Most consumers use the higher-level `fromInitData` / `fromTelegramWebApp`\n * helpers in `@takeal/cusfront-sdk/telegram`; reach for this directly when\n * you already hold a configured client. */\n async exchangeTelegram(input: { initData: string }): Promise<JwtIssued> {\n const resp = await this.http.post<JwtIssued>(\"/auth/telegram/exchange\", {\n body: { init_data: input.initData },\n skipAuth: true,\n });\n await Promise.resolve(this.tokens.set(resp.access_token));\n return resp;\n }\n\n /** Attach a real email to the current user (and clear `email_pending`).\n * Used after a Telegram exchange to satisfy the email requirement; also\n * works for a password user changing their address. 409 if taken. */\n async linkEmail(input: { email: string }): Promise<User> {\n return this.http.post<User>(\"/me/email\", { body: { email: input.email } });\n }\n\n /** Current user — fails 401 when the stored JWT is expired or\n * missing. Useful as a \"do I have a session\" probe on app boot. */\n async me(): Promise<User> {\n return this.http.get<User>(\"/auth/me\");\n }\n\n /** Local sign-out. The SDK doesn't currently call a server-side\n * endpoint (the Takeal API issues stateless JWTs); clearing the\n * store is enough. Server-side session invalidation may land\n * with a future session table. */\n async signOut(): Promise<void> {\n await Promise.resolve(this.tokens.clear());\n }\n}\n"]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { H as HttpClient, T as TokenStore } from '../http-BkZZZI8K.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auth resource — `client.auth.*`.
|
|
5
|
+
*
|
|
6
|
+
* Covers the end-user-facing staged auth flow: password login
|
|
7
|
+
* with optional TOTP/OTP step-up. The shape mirrors the responses
|
|
8
|
+
* the Takeal API returns from `/auth/login`, `/auth/login/totp`,
|
|
9
|
+
* `/auth/login/otp/verify`, `/auth/login/2fa/switch`, and `/auth/me`.
|
|
10
|
+
*
|
|
11
|
+
* The login methods stash the JWT in the configured `TokenStore` on
|
|
12
|
+
* success so subsequent `client.*` calls authenticate automatically.
|
|
13
|
+
* Step-up responses (TotpRequired / OtpSent) carry their own short-
|
|
14
|
+
* lived challenge token which the caller passes to the next method
|
|
15
|
+
* — those tokens DO NOT go through the TokenStore (they're not the
|
|
16
|
+
* end-state JWT).
|
|
17
|
+
*/
|
|
18
|
+
interface User {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
role: string;
|
|
22
|
+
is_active: boolean;
|
|
23
|
+
/** True for users auto-provisioned via the Telegram Mini App who still hold
|
|
24
|
+
* a synthetic placeholder email. The client should prompt for a real email
|
|
25
|
+
* and attach it via `auth.linkEmail` — this flips the flag to false. */
|
|
26
|
+
email_pending?: boolean;
|
|
27
|
+
created_at: string;
|
|
28
|
+
updated_at: string;
|
|
29
|
+
}
|
|
30
|
+
interface LoginInput {
|
|
31
|
+
email: string;
|
|
32
|
+
password: string;
|
|
33
|
+
}
|
|
34
|
+
/** Discriminated union — `stage` is the discriminator. */
|
|
35
|
+
type LoginResponse = JwtIssued | TotpRequired | TotpSetupRequired;
|
|
36
|
+
interface JwtIssued {
|
|
37
|
+
stage: "jwt";
|
|
38
|
+
access_token: string;
|
|
39
|
+
expires_at: string;
|
|
40
|
+
user: User;
|
|
41
|
+
/** True iff the user's role grants `system.view_hub`.
|
|
42
|
+
* Cusfront ignores this; the operator console uses it to gate `/dashboard`. */
|
|
43
|
+
hub_access?: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface TotpRequired {
|
|
46
|
+
stage: "totp_required";
|
|
47
|
+
challenge_token: string;
|
|
48
|
+
expires_at: string;
|
|
49
|
+
/** Alternate second factors. Pick one with `/2fa/switch`. */
|
|
50
|
+
available_methods: AvailableMethod[];
|
|
51
|
+
}
|
|
52
|
+
interface TotpSetupRequired {
|
|
53
|
+
stage: "totp_setup_required";
|
|
54
|
+
challenge_token: string;
|
|
55
|
+
expires_at: string;
|
|
56
|
+
/** OTPAuth URI rendered as a QR by the consumer. */
|
|
57
|
+
otpauth_url: string;
|
|
58
|
+
/** Same secret base32-encoded — for manual entry alongside QR. */
|
|
59
|
+
secret_base32: string;
|
|
60
|
+
}
|
|
61
|
+
interface AvailableMethod {
|
|
62
|
+
kind: "totp" | "telegram_otp" | "email_otp";
|
|
63
|
+
/** Short user-facing hint, e.g. `"Telegram (@user)"` or `"e****@example.com"`. */
|
|
64
|
+
hint?: string;
|
|
65
|
+
}
|
|
66
|
+
interface TotpVerifyInput {
|
|
67
|
+
challenge_token: string;
|
|
68
|
+
/** 6-digit TOTP code OR `XXXX-XXXX` backup code. */
|
|
69
|
+
code: string;
|
|
70
|
+
}
|
|
71
|
+
interface OtpVerifyInput {
|
|
72
|
+
challenge_token: string;
|
|
73
|
+
/** 6-digit one-time code delivered via the selected channel. */
|
|
74
|
+
code: string;
|
|
75
|
+
}
|
|
76
|
+
interface SwitchMethodInput {
|
|
77
|
+
challenge_token: string;
|
|
78
|
+
method: AvailableMethod["kind"];
|
|
79
|
+
}
|
|
80
|
+
interface OtpSentResponse {
|
|
81
|
+
stage: "otp_sent";
|
|
82
|
+
challenge_token: string;
|
|
83
|
+
expires_at: string;
|
|
84
|
+
method: AvailableMethod["kind"];
|
|
85
|
+
hint?: string;
|
|
86
|
+
available_methods: AvailableMethod[];
|
|
87
|
+
}
|
|
88
|
+
declare class AuthResource {
|
|
89
|
+
private readonly http;
|
|
90
|
+
private readonly tokens;
|
|
91
|
+
constructor(http: HttpClient, tokens: TokenStore);
|
|
92
|
+
/** Password-auth entry. May resolve to a JWT, or to a step-up
|
|
93
|
+
* challenge that needs `verifyTotp` / `verifyOtp` next. */
|
|
94
|
+
login(input: LoginInput): Promise<LoginResponse>;
|
|
95
|
+
/** Verify a TOTP code (or XXXX-XXXX backup code) against a
|
|
96
|
+
* `totp_required` challenge. On success the JWT is stored. */
|
|
97
|
+
verifyTotp(input: TotpVerifyInput): Promise<JwtIssued>;
|
|
98
|
+
/** Verify a 6-digit OTP code (Telegram / email) against an
|
|
99
|
+
* `otp_sent` challenge. */
|
|
100
|
+
verifyOtp(input: OtpVerifyInput): Promise<JwtIssued>;
|
|
101
|
+
/** Swap the 2FA method mid-flow. Resolves to `otp_sent` for
|
|
102
|
+
* telegram/email; for TOTP it short-circuits back to the challenge. */
|
|
103
|
+
switchMethod(input: SwitchMethodInput): Promise<OtpSentResponse>;
|
|
104
|
+
/** Exchange a signed Telegram Mini App `initData` payload for a session
|
|
105
|
+
* JWT. The server validates the initData HMAC against the deployment's bot
|
|
106
|
+
* token, then find-or-creates the end-user. First-time Telegram users are
|
|
107
|
+
* auto-provisioned with `user.email_pending === true` — prompt them for a
|
|
108
|
+
* real email and call {@link linkEmail}. Stores the JWT on success.
|
|
109
|
+
*
|
|
110
|
+
* Most consumers use the higher-level `fromInitData` / `fromTelegramWebApp`
|
|
111
|
+
* helpers in `@takeal/cusfront-sdk/telegram`; reach for this directly when
|
|
112
|
+
* you already hold a configured client. */
|
|
113
|
+
exchangeTelegram(input: {
|
|
114
|
+
initData: string;
|
|
115
|
+
}): Promise<JwtIssued>;
|
|
116
|
+
/** Attach a real email to the current user (and clear `email_pending`).
|
|
117
|
+
* Used after a Telegram exchange to satisfy the email requirement; also
|
|
118
|
+
* works for a password user changing their address. 409 if taken. */
|
|
119
|
+
linkEmail(input: {
|
|
120
|
+
email: string;
|
|
121
|
+
}): Promise<User>;
|
|
122
|
+
/** Current user — fails 401 when the stored JWT is expired or
|
|
123
|
+
* missing. Useful as a "do I have a session" probe on app boot. */
|
|
124
|
+
me(): Promise<User>;
|
|
125
|
+
/** Local sign-out. The SDK doesn't currently call a server-side
|
|
126
|
+
* endpoint (the Takeal API issues stateless JWTs); clearing the
|
|
127
|
+
* store is enough. Server-side session invalidation may land
|
|
128
|
+
* with a future session table. */
|
|
129
|
+
signOut(): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { AuthResource, type AvailableMethod, type JwtIssued, type LoginInput, type LoginResponse, type OtpSentResponse, type OtpVerifyInput, type SwitchMethodInput, type TotpRequired, type TotpSetupRequired, type TotpVerifyInput, type User };
|