@viyv/account-client 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.
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ // src/entitlement.ts
4
+ function parseEntitlement(wire) {
5
+ return {
6
+ organizationId: wire.organization_id,
7
+ plan: wire.plan,
8
+ addons: wire.addons ?? [],
9
+ products: wire.products ?? [],
10
+ features: wire.features
11
+ };
12
+ }
13
+
14
+ // src/introspect.ts
15
+ var DEFAULT_TIMEOUT_MS = 5e3;
16
+ function createIntrospectionClient(config) {
17
+ const f = config.fetchImpl ?? fetch;
18
+ const base = config.apiBase.replace(/\/$/, "");
19
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20
+ const secretHeaders = {
21
+ "x-viyv-service-secret": config.serviceSecret
22
+ };
23
+ if (config.clientId) secretHeaders["x-viyv-client-id"] = config.clientId;
24
+ async function call(path, init) {
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
27
+ try {
28
+ return await f(`${base}${path}`, { ...init, signal: controller.signal });
29
+ } finally {
30
+ clearTimeout(timer);
31
+ }
32
+ }
33
+ return {
34
+ async introspectDevice(accessToken) {
35
+ let res;
36
+ try {
37
+ res = await call("/v1/auth/device/introspect", {
38
+ method: "POST",
39
+ headers: {
40
+ ...secretHeaders,
41
+ authorization: `Bearer ${accessToken}`,
42
+ "content-type": "application/json"
43
+ },
44
+ body: "{}"
45
+ });
46
+ } catch (err) {
47
+ return { status: "unavailable", reason: `introspect transport error: ${errMessage(err)}` };
48
+ }
49
+ if (res.status === 401) {
50
+ return {
51
+ status: "unavailable",
52
+ reason: "introspect unauthorized (service secret / server config)"
53
+ };
54
+ }
55
+ if (!res.ok) {
56
+ return { status: "unavailable", reason: `introspect http ${res.status}` };
57
+ }
58
+ let raw;
59
+ try {
60
+ raw = await res.json();
61
+ } catch (err) {
62
+ return { status: "integrity_error", reason: `introspect unparseable body: ${errMessage(err)}` };
63
+ }
64
+ if (!raw || typeof raw !== "object") {
65
+ return { status: "integrity_error", reason: "introspect non-object body" };
66
+ }
67
+ const body = raw;
68
+ if (body.active !== true) {
69
+ return { status: "inactive" };
70
+ }
71
+ if (!body.org_id || !body.user_id) {
72
+ return { status: "integrity_error", reason: "active token missing org_id/user_id" };
73
+ }
74
+ return {
75
+ status: "active",
76
+ subject: {
77
+ orgId: body.org_id,
78
+ userId: body.user_id,
79
+ deviceId: body.device_id ?? null,
80
+ scope: body.scope,
81
+ expiresAtUnix: body.exp,
82
+ clientId: body.client_id ?? null,
83
+ email: body.email ?? null,
84
+ name: body.name ?? null,
85
+ orgName: body.org_name ?? null
86
+ }
87
+ };
88
+ },
89
+ async getOrgEntitlement(orgId) {
90
+ let res;
91
+ try {
92
+ res = await call(`/v1/license/entitlement/by-org?org_id=${encodeURIComponent(orgId)}`, {
93
+ method: "GET",
94
+ headers: secretHeaders
95
+ });
96
+ } catch (err) {
97
+ return { status: "unavailable", reason: `entitlement transport error: ${errMessage(err)}` };
98
+ }
99
+ if (!res.ok) {
100
+ return { status: "unavailable", reason: `entitlement http ${res.status}` };
101
+ }
102
+ try {
103
+ const wire = await res.json();
104
+ return { status: "ok", entitlement: parseEntitlement(wire) };
105
+ } catch (err) {
106
+ return { status: "unavailable", reason: `entitlement unparseable body: ${errMessage(err)}` };
107
+ }
108
+ }
109
+ };
110
+ }
111
+ function errMessage(err) {
112
+ return err instanceof Error ? err.message : String(err);
113
+ }
114
+
115
+ exports.createIntrospectionClient = createIntrospectionClient;
116
+ //# sourceMappingURL=introspect.cjs.map
117
+ //# sourceMappingURL=introspect.cjs.map
@@ -0,0 +1,67 @@
1
+ import { E as Entitlement } from './entitlement-B3pXM2vO.cjs';
2
+ import '@viyv/shared';
3
+
4
+ interface IntrospectionClientConfig {
5
+ /** e.g. "https://api.viyv.io". */
6
+ apiBase: string;
7
+ /**
8
+ * Shared m2m secret (== viyv-account `INTERNAL_SERVICE_SECRET`). CALLER-INJECTED
9
+ * on purpose: this lib never reads it from the environment, so it can never
10
+ * bundle or log the secret. Pass it from your own server config.
11
+ */
12
+ serviceSecret: string;
13
+ /** Optional caller id, sent as `x-viyv-client-id` for the server's audit log. */
14
+ clientId?: string;
15
+ /** Injectable fetch (Node). Defaults to global fetch. */
16
+ fetchImpl?: typeof fetch;
17
+ /** Per-request timeout (ms). Default 5000. A timeout maps to `unavailable`. */
18
+ timeoutMs?: number;
19
+ }
20
+ /** Subject resolved from an ACTIVE device access token (camelCase). */
21
+ interface ResolvedDeviceSubject {
22
+ orgId: string;
23
+ userId: string;
24
+ deviceId: string | null;
25
+ scope: string;
26
+ /** Access-token expiry, seconds since the Unix epoch. */
27
+ expiresAtUnix: number;
28
+ clientId: string | null;
29
+ email: string | null;
30
+ name: string | null;
31
+ orgName: string | null;
32
+ }
33
+ /**
34
+ * Discriminated outcome of introspecting a device access token. Consumers MUST
35
+ * branch on every case — that is the point: the type system forces handling of
36
+ * the tamper (`integrity_error`) and `unavailable` cases that hand-rolled
37
+ * clients have historically conflated with `inactive`.
38
+ */
39
+ type IntrospectionOutcome = {
40
+ status: "active";
41
+ subject: ResolvedDeviceSubject;
42
+ } | {
43
+ status: "inactive";
44
+ } | {
45
+ status: "unavailable";
46
+ reason: string;
47
+ } | {
48
+ status: "integrity_error";
49
+ reason: string;
50
+ };
51
+ /** Outcome of an m2m org-entitlement read. */
52
+ type OrgEntitlementOutcome = {
53
+ status: "ok";
54
+ entitlement: Entitlement;
55
+ } | {
56
+ status: "unavailable";
57
+ reason: string;
58
+ };
59
+ interface IntrospectionClient {
60
+ /** Resolve a device access token to a subject (fail-closed, see taxonomy). */
61
+ introspectDevice(accessToken: string): Promise<IntrospectionOutcome>;
62
+ /** Read an org's entitlement by id (m2m). */
63
+ getOrgEntitlement(orgId: string): Promise<OrgEntitlementOutcome>;
64
+ }
65
+ declare function createIntrospectionClient(config: IntrospectionClientConfig): IntrospectionClient;
66
+
67
+ export { type IntrospectionClient, type IntrospectionClientConfig, type IntrospectionOutcome, type OrgEntitlementOutcome, type ResolvedDeviceSubject, createIntrospectionClient };
@@ -0,0 +1,67 @@
1
+ import { E as Entitlement } from './entitlement-B3pXM2vO.js';
2
+ import '@viyv/shared';
3
+
4
+ interface IntrospectionClientConfig {
5
+ /** e.g. "https://api.viyv.io". */
6
+ apiBase: string;
7
+ /**
8
+ * Shared m2m secret (== viyv-account `INTERNAL_SERVICE_SECRET`). CALLER-INJECTED
9
+ * on purpose: this lib never reads it from the environment, so it can never
10
+ * bundle or log the secret. Pass it from your own server config.
11
+ */
12
+ serviceSecret: string;
13
+ /** Optional caller id, sent as `x-viyv-client-id` for the server's audit log. */
14
+ clientId?: string;
15
+ /** Injectable fetch (Node). Defaults to global fetch. */
16
+ fetchImpl?: typeof fetch;
17
+ /** Per-request timeout (ms). Default 5000. A timeout maps to `unavailable`. */
18
+ timeoutMs?: number;
19
+ }
20
+ /** Subject resolved from an ACTIVE device access token (camelCase). */
21
+ interface ResolvedDeviceSubject {
22
+ orgId: string;
23
+ userId: string;
24
+ deviceId: string | null;
25
+ scope: string;
26
+ /** Access-token expiry, seconds since the Unix epoch. */
27
+ expiresAtUnix: number;
28
+ clientId: string | null;
29
+ email: string | null;
30
+ name: string | null;
31
+ orgName: string | null;
32
+ }
33
+ /**
34
+ * Discriminated outcome of introspecting a device access token. Consumers MUST
35
+ * branch on every case — that is the point: the type system forces handling of
36
+ * the tamper (`integrity_error`) and `unavailable` cases that hand-rolled
37
+ * clients have historically conflated with `inactive`.
38
+ */
39
+ type IntrospectionOutcome = {
40
+ status: "active";
41
+ subject: ResolvedDeviceSubject;
42
+ } | {
43
+ status: "inactive";
44
+ } | {
45
+ status: "unavailable";
46
+ reason: string;
47
+ } | {
48
+ status: "integrity_error";
49
+ reason: string;
50
+ };
51
+ /** Outcome of an m2m org-entitlement read. */
52
+ type OrgEntitlementOutcome = {
53
+ status: "ok";
54
+ entitlement: Entitlement;
55
+ } | {
56
+ status: "unavailable";
57
+ reason: string;
58
+ };
59
+ interface IntrospectionClient {
60
+ /** Resolve a device access token to a subject (fail-closed, see taxonomy). */
61
+ introspectDevice(accessToken: string): Promise<IntrospectionOutcome>;
62
+ /** Read an org's entitlement by id (m2m). */
63
+ getOrgEntitlement(orgId: string): Promise<OrgEntitlementOutcome>;
64
+ }
65
+ declare function createIntrospectionClient(config: IntrospectionClientConfig): IntrospectionClient;
66
+
67
+ export { type IntrospectionClient, type IntrospectionClientConfig, type IntrospectionOutcome, type OrgEntitlementOutcome, type ResolvedDeviceSubject, createIntrospectionClient };
@@ -0,0 +1,106 @@
1
+ import { parseEntitlement } from './chunk-UX6UAFIF.js';
2
+
3
+ // src/introspect.ts
4
+ var DEFAULT_TIMEOUT_MS = 5e3;
5
+ function createIntrospectionClient(config) {
6
+ const f = config.fetchImpl ?? fetch;
7
+ const base = config.apiBase.replace(/\/$/, "");
8
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
9
+ const secretHeaders = {
10
+ "x-viyv-service-secret": config.serviceSecret
11
+ };
12
+ if (config.clientId) secretHeaders["x-viyv-client-id"] = config.clientId;
13
+ async function call(path, init) {
14
+ const controller = new AbortController();
15
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
16
+ try {
17
+ return await f(`${base}${path}`, { ...init, signal: controller.signal });
18
+ } finally {
19
+ clearTimeout(timer);
20
+ }
21
+ }
22
+ return {
23
+ async introspectDevice(accessToken) {
24
+ let res;
25
+ try {
26
+ res = await call("/v1/auth/device/introspect", {
27
+ method: "POST",
28
+ headers: {
29
+ ...secretHeaders,
30
+ authorization: `Bearer ${accessToken}`,
31
+ "content-type": "application/json"
32
+ },
33
+ body: "{}"
34
+ });
35
+ } catch (err) {
36
+ return { status: "unavailable", reason: `introspect transport error: ${errMessage(err)}` };
37
+ }
38
+ if (res.status === 401) {
39
+ return {
40
+ status: "unavailable",
41
+ reason: "introspect unauthorized (service secret / server config)"
42
+ };
43
+ }
44
+ if (!res.ok) {
45
+ return { status: "unavailable", reason: `introspect http ${res.status}` };
46
+ }
47
+ let raw;
48
+ try {
49
+ raw = await res.json();
50
+ } catch (err) {
51
+ return { status: "integrity_error", reason: `introspect unparseable body: ${errMessage(err)}` };
52
+ }
53
+ if (!raw || typeof raw !== "object") {
54
+ return { status: "integrity_error", reason: "introspect non-object body" };
55
+ }
56
+ const body = raw;
57
+ if (body.active !== true) {
58
+ return { status: "inactive" };
59
+ }
60
+ if (!body.org_id || !body.user_id) {
61
+ return { status: "integrity_error", reason: "active token missing org_id/user_id" };
62
+ }
63
+ return {
64
+ status: "active",
65
+ subject: {
66
+ orgId: body.org_id,
67
+ userId: body.user_id,
68
+ deviceId: body.device_id ?? null,
69
+ scope: body.scope,
70
+ expiresAtUnix: body.exp,
71
+ clientId: body.client_id ?? null,
72
+ email: body.email ?? null,
73
+ name: body.name ?? null,
74
+ orgName: body.org_name ?? null
75
+ }
76
+ };
77
+ },
78
+ async getOrgEntitlement(orgId) {
79
+ let res;
80
+ try {
81
+ res = await call(`/v1/license/entitlement/by-org?org_id=${encodeURIComponent(orgId)}`, {
82
+ method: "GET",
83
+ headers: secretHeaders
84
+ });
85
+ } catch (err) {
86
+ return { status: "unavailable", reason: `entitlement transport error: ${errMessage(err)}` };
87
+ }
88
+ if (!res.ok) {
89
+ return { status: "unavailable", reason: `entitlement http ${res.status}` };
90
+ }
91
+ try {
92
+ const wire = await res.json();
93
+ return { status: "ok", entitlement: parseEntitlement(wire) };
94
+ } catch (err) {
95
+ return { status: "unavailable", reason: `entitlement unparseable body: ${errMessage(err)}` };
96
+ }
97
+ }
98
+ };
99
+ }
100
+ function errMessage(err) {
101
+ return err instanceof Error ? err.message : String(err);
102
+ }
103
+
104
+ export { createIntrospectionClient };
105
+ //# sourceMappingURL=introspect.js.map
106
+ //# sourceMappingURL=introspect.js.map
package/dist/react.cjs ADDED
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var client = require('better-auth/client');
5
+
6
+ // src/entitlement.ts
7
+ function parseEntitlement(wire) {
8
+ return {
9
+ organizationId: wire.organization_id,
10
+ plan: wire.plan,
11
+ addons: wire.addons ?? [],
12
+ products: wire.products ?? [],
13
+ features: wire.features
14
+ };
15
+ }
16
+ function entitlementForProduct(entitlement, product) {
17
+ if (!entitlement) return null;
18
+ return {
19
+ enabled: entitlement.products.includes(product),
20
+ plan: entitlement.plan,
21
+ addons: entitlement.addons,
22
+ features: entitlement.features
23
+ };
24
+ }
25
+ function createAccountClient(config) {
26
+ const client$1 = client.createAuthClient({
27
+ baseURL: config.apiBase,
28
+ basePath: "/v1/auth",
29
+ fetchOptions: { credentials: "include" }
30
+ });
31
+ return {
32
+ raw: client$1,
33
+ signUp: (params) => client$1.signUp.email({
34
+ email: params.email,
35
+ password: params.password,
36
+ name: params.name ?? params.email.split("@")[0] ?? params.email
37
+ }),
38
+ signIn: (params) => client$1.signIn.email({
39
+ email: params.email,
40
+ password: params.password
41
+ }),
42
+ signOut: () => client$1.signOut(),
43
+ getSession: () => client$1.getSession()
44
+ };
45
+ }
46
+
47
+ // src/provider.tsx
48
+ async function fetchJson(doFetch, url, signal) {
49
+ const res = await doFetch(url, { credentials: "include", signal });
50
+ if (res.status === 401 || res.status === 404) return null;
51
+ if (!res.ok) throw new Error(`${url} \u2192 ${res.status}`);
52
+ const text = await res.text();
53
+ if (!text || text === "null") return null;
54
+ return JSON.parse(text);
55
+ }
56
+ var SIGNED_OUT = {
57
+ status: "unauthenticated",
58
+ user: null,
59
+ activeOrg: null,
60
+ entitlement: null
61
+ };
62
+ async function loadAccount(apiBase, opts = {}) {
63
+ const doFetch = opts.fetchImpl ?? fetch;
64
+ const session = await fetchJson(
65
+ doFetch,
66
+ `${apiBase}/v1/auth/get-session`,
67
+ opts.signal
68
+ );
69
+ if (!session?.user) return SIGNED_OUT;
70
+ const user = {
71
+ id: session.user.id,
72
+ email: session.user.email,
73
+ name: session.user.name ?? null,
74
+ image: session.user.image ?? null
75
+ };
76
+ const [ent, orgsBody] = await Promise.all([
77
+ opts.skipEntitlement ? Promise.resolve(null) : fetchJson(
78
+ doFetch,
79
+ `${apiBase}/v1/license/entitlement`,
80
+ opts.signal
81
+ ),
82
+ fetchJson(doFetch, `${apiBase}/v1/users/me/orgs`, opts.signal)
83
+ ]);
84
+ const entitlement = ent ? parseEntitlement(ent) : null;
85
+ const activeId = session.session?.activeOrganizationId ?? entitlement?.organizationId ?? null;
86
+ const orgs = orgsBody?.organizations ?? [];
87
+ const match = activeId ? orgs.find((o) => o.organization_id === activeId) : orgs[0];
88
+ const activeOrg = match ? { id: match.organization_id, slug: match.slug ?? null, name: match.name ?? null } : activeId ? { id: activeId, slug: null, name: null } : null;
89
+ return { status: "authenticated", user, activeOrg, entitlement };
90
+ }
91
+ var AccountContext = react.createContext(null);
92
+ function AccountProvider({
93
+ apiBase,
94
+ cookieDomain,
95
+ signInUrl = "https://app.viyv.io/signin",
96
+ skipEntitlement = false,
97
+ children
98
+ }) {
99
+ const clientRef = react.useRef(null);
100
+ if (!clientRef.current) {
101
+ clientRef.current = createAccountClient({ apiBase});
102
+ }
103
+ const [status, setStatus] = react.useState("loading");
104
+ const [user, setUser] = react.useState(null);
105
+ const [activeOrg, setActiveOrg] = react.useState(null);
106
+ const [entitlement, setEntitlement] = react.useState(null);
107
+ const [error, setError] = react.useState(null);
108
+ const load = react.useCallback(
109
+ async (signal) => {
110
+ try {
111
+ setError(null);
112
+ const result = await loadAccount(apiBase, { skipEntitlement, signal });
113
+ setUser(result.user);
114
+ setActiveOrg(result.activeOrg);
115
+ setEntitlement(result.entitlement);
116
+ setStatus(result.status);
117
+ } catch (err) {
118
+ if (signal?.aborted) return;
119
+ setError(err instanceof Error ? err : new Error(String(err)));
120
+ setUser(null);
121
+ setActiveOrg(null);
122
+ setEntitlement(null);
123
+ setStatus("unauthenticated");
124
+ }
125
+ },
126
+ [apiBase, skipEntitlement]
127
+ );
128
+ react.useEffect(() => {
129
+ const ctrl = new AbortController();
130
+ void load(ctrl.signal);
131
+ return () => ctrl.abort();
132
+ }, [load]);
133
+ const signIn = react.useCallback(
134
+ (returnTo) => {
135
+ if (typeof window === "undefined") return;
136
+ const back = returnTo ?? window.location.href;
137
+ window.location.assign(`${signInUrl}?return_to=${encodeURIComponent(back)}`);
138
+ },
139
+ [signInUrl]
140
+ );
141
+ const signOut = react.useCallback(async () => {
142
+ await clientRef.current?.signOut();
143
+ setUser(null);
144
+ setActiveOrg(null);
145
+ setEntitlement(null);
146
+ setStatus("unauthenticated");
147
+ }, []);
148
+ const value = react.useMemo(
149
+ () => ({
150
+ status,
151
+ user,
152
+ activeOrg,
153
+ entitlement,
154
+ error,
155
+ refresh: () => load(),
156
+ signIn,
157
+ signOut,
158
+ apiBase,
159
+ signInUrl
160
+ }),
161
+ [status, user, activeOrg, entitlement, error, load, signIn, signOut, apiBase, signInUrl]
162
+ );
163
+ return react.createElement(AccountContext.Provider, { value }, children);
164
+ }
165
+ function useAccount() {
166
+ const ctx = react.useContext(AccountContext);
167
+ if (!ctx) throw new Error("useAccount must be used within <AccountProvider>");
168
+ return ctx;
169
+ }
170
+ function useUser() {
171
+ return useAccount().user;
172
+ }
173
+ function useActiveOrg() {
174
+ return useAccount().activeOrg;
175
+ }
176
+ function useEntitlement(product) {
177
+ const { entitlement } = useAccount();
178
+ return react.useMemo(() => entitlementForProduct(entitlement, product), [entitlement, product]);
179
+ }
180
+
181
+ // src/components.tsx
182
+ function SignInButton({ returnTo, children, className }) {
183
+ const { signIn } = useAccount();
184
+ return react.createElement(
185
+ "button",
186
+ { type: "button", className, onClick: () => signIn(returnTo) },
187
+ children ?? "Sign in"
188
+ );
189
+ }
190
+ function SignOutButton({ children, className, onSignedOut }) {
191
+ const { signOut } = useAccount();
192
+ return react.createElement(
193
+ "button",
194
+ {
195
+ type: "button",
196
+ className,
197
+ onClick: async () => {
198
+ await signOut();
199
+ onSignedOut?.();
200
+ }
201
+ },
202
+ children ?? "Sign out"
203
+ );
204
+ }
205
+
206
+ exports.AccountProvider = AccountProvider;
207
+ exports.SignInButton = SignInButton;
208
+ exports.SignOutButton = SignOutButton;
209
+ exports.useAccount = useAccount;
210
+ exports.useActiveOrg = useActiveOrg;
211
+ exports.useEntitlement = useEntitlement;
212
+ exports.useUser = useUser;
213
+ //# sourceMappingURL=react.cjs.map
214
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1,102 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { PlanTier, ProductSlug, EntitlementFeatures } from '@viyv/shared';
4
+ export { EntitlementFeatures, PlanTier, ProductSlug } from '@viyv/shared';
5
+
6
+ interface SignInButtonProps {
7
+ /** Where to return after sign-in. Default: current URL. */
8
+ returnTo?: string;
9
+ children?: ReactNode;
10
+ className?: string;
11
+ }
12
+ /** Redirects to the hosted sign-in page (app.viyv.io) with a return_to. */
13
+ declare function SignInButton({ returnTo, children, className }: SignInButtonProps): react.DetailedReactHTMLElement<{
14
+ type: string;
15
+ className: string | undefined;
16
+ onClick: () => void;
17
+ }, HTMLElement>;
18
+ interface SignOutButtonProps {
19
+ children?: ReactNode;
20
+ className?: string;
21
+ onSignedOut?: () => void;
22
+ }
23
+ /** Signs out via better-auth and clears local account state. */
24
+ declare function SignOutButton({ children, className, onSignedOut }: SignOutButtonProps): react.DetailedReactHTMLElement<{
25
+ type: string;
26
+ className: string | undefined;
27
+ onClick: () => Promise<void>;
28
+ }, HTMLElement>;
29
+
30
+ /** The signed-in user as returned by better-auth's get-session. */
31
+ interface AccountUser {
32
+ id: string;
33
+ email: string;
34
+ name?: string | null;
35
+ image?: string | null;
36
+ }
37
+ /** Active organization, when the session carries one. */
38
+ interface AccountOrg {
39
+ id: string;
40
+ slug?: string | null;
41
+ name?: string | null;
42
+ }
43
+ /** Shape of GET /v1/license/entitlement (docs/09 A-4). */
44
+ interface Entitlement {
45
+ organizationId: string;
46
+ plan: PlanTier;
47
+ addons: string[];
48
+ /** Product slugs this org may use (free-start lists the agent family). */
49
+ products: ProductSlug[];
50
+ features: EntitlementFeatures;
51
+ }
52
+ /** Per-product entitlement view returned by useEntitlement(slug). */
53
+ interface ProductEntitlement {
54
+ /** Whether the product is enabled for this org. */
55
+ enabled: boolean;
56
+ plan: PlanTier;
57
+ addons: string[];
58
+ /** The full feature map (callers branch on the flags they care about). */
59
+ features: EntitlementFeatures;
60
+ }
61
+
62
+ type AccountStatus = "loading" | "authenticated" | "unauthenticated";
63
+ interface AccountContextValue {
64
+ status: AccountStatus;
65
+ user: AccountUser | null;
66
+ activeOrg: AccountOrg | null;
67
+ entitlement: Entitlement | null;
68
+ error: Error | null;
69
+ /** Re-fetch session + entitlement (e.g. after returning from checkout). */
70
+ refresh: () => Promise<void>;
71
+ /** Redirect to the hosted sign-in page (app.viyv.io) with a return_to. */
72
+ signIn: (returnTo?: string) => void;
73
+ /** Sign out via better-auth and drop local state. */
74
+ signOut: () => Promise<void>;
75
+ apiBase: string;
76
+ signInUrl: string;
77
+ }
78
+ interface AccountProviderProps {
79
+ /** e.g. "https://api.viyv.io" */
80
+ apiBase: string;
81
+ /** e.g. ".viyv.io" — kept for parity with docs/04 (cookies are server-set). */
82
+ cookieDomain?: string;
83
+ /** Hosted sign-in page. Default "https://app.viyv.io/signin". */
84
+ signInUrl?: string;
85
+ /** Skip the entitlement fetch (session-only consumers). Default false. */
86
+ skipEntitlement?: boolean;
87
+ children?: ReactNode;
88
+ }
89
+ declare function AccountProvider({ apiBase, cookieDomain, signInUrl, skipEntitlement, children, }: AccountProviderProps): react.FunctionComponentElement<react.ProviderProps<AccountContextValue | null>>;
90
+ /** Read the full account context. Throws if used outside <AccountProvider>. */
91
+ declare function useAccount(): AccountContextValue;
92
+ /** The signed-in user, or null when unauthenticated/loading. */
93
+ declare function useUser(): AccountUser | null;
94
+ /** The active organization, or null. */
95
+ declare function useActiveOrg(): AccountOrg | null;
96
+ /**
97
+ * Per-product entitlement view. Returns null while loading / unauthenticated.
98
+ * Web products gate features on the returned `enabled` + `features` flags.
99
+ */
100
+ declare function useEntitlement(product: ProductSlug): ProductEntitlement | null;
101
+
102
+ export { type AccountContextValue, type AccountOrg, AccountProvider, type AccountProviderProps, type AccountStatus, type AccountUser, type Entitlement, type ProductEntitlement, SignInButton, type SignInButtonProps, SignOutButton, type SignOutButtonProps, useAccount, useActiveOrg, useEntitlement, useUser };