@oxyhq/auth 2.0.4 → 2.0.6
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/WebOxyProvider.js +37 -0
- package/dist/cjs/hooks/mutations/index.js +5 -1
- package/dist/cjs/hooks/mutations/useAccountMutations.js +185 -43
- package/dist/cjs/hooks/mutations/useAppData.js +133 -0
- package/dist/cjs/hooks/queries/appDataQueryKeys.js +46 -0
- package/dist/cjs/hooks/queries/index.js +8 -1
- package/dist/cjs/hooks/queries/useAppData.js +87 -0
- package/dist/cjs/hooks/queryClient.js +136 -92
- package/dist/cjs/hooks/useFileDownloadUrl.js +12 -36
- package/dist/cjs/hooks/useSessionSocket.js +81 -94
- package/dist/cjs/index.js +8 -3
- package/dist/cjs/utils/sessionHelpers.js +3 -1
- package/dist/cjs/utils/storageHelpers.js +36 -10
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/WebOxyProvider.js +38 -1
- package/dist/esm/hooks/mutations/index.js +2 -0
- package/dist/esm/hooks/mutations/useAccountMutations.js +186 -44
- package/dist/esm/hooks/mutations/useAppData.js +128 -0
- package/dist/esm/hooks/queries/appDataQueryKeys.js +42 -0
- package/dist/esm/hooks/queries/index.js +3 -0
- package/dist/esm/hooks/queries/useAppData.js +82 -0
- package/dist/esm/hooks/queryClient.js +132 -89
- package/dist/esm/hooks/useFileDownloadUrl.js +11 -34
- package/dist/esm/hooks/useSessionSocket.js +81 -94
- package/dist/esm/index.js +3 -3
- package/dist/esm/utils/sessionHelpers.js +3 -1
- package/dist/esm/utils/storageHelpers.js +36 -10
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/WebOxyProvider.d.ts +1 -1
- package/dist/types/hooks/mutations/index.d.ts +1 -0
- package/dist/types/hooks/mutations/useAccountMutations.d.ts +153 -9
- package/dist/types/hooks/mutations/useAppData.d.ts +47 -0
- package/dist/types/hooks/queries/appDataQueryKeys.d.ts +24 -0
- package/dist/types/hooks/queries/index.d.ts +2 -0
- package/dist/types/hooks/queries/useAccountQueries.d.ts +11 -7
- package/dist/types/hooks/queries/useAppData.d.ts +46 -0
- package/dist/types/hooks/queries/useSecurityQueries.d.ts +2 -2
- package/dist/types/hooks/queries/useServicesQueries.d.ts +7 -5
- package/dist/types/hooks/queryClient.d.ts +24 -10
- package/dist/types/hooks/useAssets.d.ts +1 -1
- package/dist/types/hooks/useFileDownloadUrl.d.ts +2 -6
- package/dist/types/index.d.ts +3 -3
- package/dist/types/utils/sessionHelpers.d.ts +3 -1
- package/package.json +22 -3
- package/src/WebOxyProvider.tsx +39 -1
- package/src/hooks/mutations/index.ts +3 -0
- package/src/hooks/mutations/useAccountMutations.ts +230 -57
- package/src/hooks/mutations/useAppData.ts +167 -0
- package/src/hooks/queries/appDataQueryKeys.ts +53 -0
- package/src/hooks/queries/index.ts +4 -0
- package/src/hooks/queries/useAppData.ts +105 -0
- package/src/hooks/queryClient.ts +140 -83
- package/src/hooks/useFileDownloadUrl.ts +15 -39
- package/src/hooks/useSessionSocket.ts +123 -91
- package/src/index.ts +7 -1
- package/src/utils/sessionHelpers.ts +3 -1
- package/src/utils/storageHelpers.ts +49 -10
|
@@ -59,7 +59,7 @@ export interface WebOxyProviderProps {
|
|
|
59
59
|
* }
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
|
-
export declare function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onError, preferredAuthMethod, skipAutoCheck, }: WebOxyProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
export declare function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onError, preferredAuthMethod, skipAutoCheck, }: WebOxyProviderProps): import("react/jsx-runtime").JSX.Element | null;
|
|
63
63
|
/**
|
|
64
64
|
* Hook to access the full Web Oxy context.
|
|
65
65
|
*/
|
|
@@ -6,3 +6,4 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { useUpdateProfile, useUploadAvatar, useUpdateAccountSettings, useUpdatePrivacySettings, useUploadFile, } from './useAccountMutations';
|
|
8
8
|
export { useSwitchSession, useLogoutSession, useLogoutAll, useUpdateDeviceName, useRemoveDevice, } from './useServicesMutations';
|
|
9
|
+
export { useSetAppData, useDeleteAppData } from './useAppData';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { User } from '@oxyhq/core';
|
|
1
|
+
import type { PrivacySettings, User } from '@oxyhq/core';
|
|
2
2
|
/**
|
|
3
3
|
* Update user profile with optimistic updates and offline queue support
|
|
4
4
|
*/
|
|
@@ -12,19 +12,163 @@ export declare const useUploadAvatar: () => import("@tanstack/react-query").UseM
|
|
|
12
12
|
previousUser: User | undefined;
|
|
13
13
|
}>;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Variables accepted by the `useUpdateAccountSettings` mutation.
|
|
16
|
+
*
|
|
17
|
+
* `currentUser` is captured at dispatch time so the rebuilt user object the
|
|
18
|
+
* mutation returns is computed against a stable snapshot — NOT the cache
|
|
19
|
+
* value at the moment the API call settles. Reading from the cache inside
|
|
20
|
+
* `mutationFn` would race with sibling optimistic updates: a concurrent
|
|
21
|
+
* write could already have overwritten the cache by the time the privacy
|
|
22
|
+
* update returns, causing the rebuilt user to clobber the sibling's
|
|
23
|
+
* optimistic value.
|
|
16
24
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
interface UpdateAccountSettingsVariables {
|
|
26
|
+
updates: Partial<PrivacySettings>;
|
|
27
|
+
currentUser: User;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Update account settings (privacy preferences).
|
|
31
|
+
*
|
|
32
|
+
* Privacy settings are not part of the `PUT /users/me` allow-list; the API
|
|
33
|
+
* would silently drop them. Route through `updatePrivacySettings` so the
|
|
34
|
+
* dedicated `PATCH /privacy/:id/privacy` endpoint performs a dot-path merge
|
|
35
|
+
* and returns the updated `privacySettings` object.
|
|
36
|
+
*
|
|
37
|
+
* The returned object exposes the standard mutation surface PLUS a
|
|
38
|
+
* convenience `mutate(updates)` / `mutateAsync(updates)` that snapshots
|
|
39
|
+
* the current user from `useWebOxy()` at dispatch time.
|
|
40
|
+
*/
|
|
41
|
+
export declare const useUpdateAccountSettings: () => {
|
|
42
|
+
mutate: (updates: Partial<PrivacySettings>) => void;
|
|
43
|
+
mutateAsync: (updates: Partial<PrivacySettings>) => Promise<User>;
|
|
44
|
+
data: undefined;
|
|
45
|
+
variables: undefined;
|
|
46
|
+
error: null;
|
|
47
|
+
isError: false;
|
|
48
|
+
isIdle: true;
|
|
49
|
+
isPending: false;
|
|
50
|
+
isSuccess: false;
|
|
51
|
+
status: "idle";
|
|
52
|
+
reset: () => void;
|
|
53
|
+
context: {
|
|
54
|
+
previousUser: User | undefined;
|
|
55
|
+
} | undefined;
|
|
56
|
+
failureCount: number;
|
|
57
|
+
failureReason: Error | null;
|
|
58
|
+
isPaused: boolean;
|
|
59
|
+
submittedAt: number;
|
|
60
|
+
} | {
|
|
61
|
+
mutate: (updates: Partial<PrivacySettings>) => void;
|
|
62
|
+
mutateAsync: (updates: Partial<PrivacySettings>) => Promise<User>;
|
|
63
|
+
data: undefined;
|
|
64
|
+
variables: UpdateAccountSettingsVariables;
|
|
65
|
+
error: null;
|
|
66
|
+
isError: false;
|
|
67
|
+
isIdle: false;
|
|
68
|
+
isPending: true;
|
|
69
|
+
isSuccess: false;
|
|
70
|
+
status: "pending";
|
|
71
|
+
reset: () => void;
|
|
72
|
+
context: {
|
|
73
|
+
previousUser: User | undefined;
|
|
74
|
+
} | undefined;
|
|
75
|
+
failureCount: number;
|
|
76
|
+
failureReason: Error | null;
|
|
77
|
+
isPaused: boolean;
|
|
78
|
+
submittedAt: number;
|
|
79
|
+
} | {
|
|
80
|
+
mutate: (updates: Partial<PrivacySettings>) => void;
|
|
81
|
+
mutateAsync: (updates: Partial<PrivacySettings>) => Promise<User>;
|
|
82
|
+
data: undefined;
|
|
83
|
+
error: Error;
|
|
84
|
+
variables: UpdateAccountSettingsVariables;
|
|
85
|
+
isError: true;
|
|
86
|
+
isIdle: false;
|
|
87
|
+
isPending: false;
|
|
88
|
+
isSuccess: false;
|
|
89
|
+
status: "error";
|
|
90
|
+
reset: () => void;
|
|
91
|
+
context: {
|
|
92
|
+
previousUser: User | undefined;
|
|
93
|
+
} | undefined;
|
|
94
|
+
failureCount: number;
|
|
95
|
+
failureReason: Error | null;
|
|
96
|
+
isPaused: boolean;
|
|
97
|
+
submittedAt: number;
|
|
98
|
+
} | {
|
|
99
|
+
mutate: (updates: Partial<PrivacySettings>) => void;
|
|
100
|
+
mutateAsync: (updates: Partial<PrivacySettings>) => Promise<User>;
|
|
101
|
+
data: {
|
|
102
|
+
privacySettings: PrivacySettings;
|
|
103
|
+
id: string;
|
|
104
|
+
publicKey: string;
|
|
105
|
+
username: string;
|
|
106
|
+
email?: string;
|
|
107
|
+
avatar?: string;
|
|
108
|
+
color?: string;
|
|
109
|
+
name?: {
|
|
110
|
+
first?: string;
|
|
111
|
+
last?: string;
|
|
112
|
+
full?: string;
|
|
113
|
+
[key: string]: unknown;
|
|
114
|
+
};
|
|
115
|
+
bio?: string;
|
|
116
|
+
karma?: number;
|
|
117
|
+
location?: string;
|
|
118
|
+
website?: string;
|
|
119
|
+
createdAt?: string;
|
|
120
|
+
updatedAt?: string;
|
|
121
|
+
links?: Array<{
|
|
122
|
+
title?: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
image?: string;
|
|
125
|
+
link: string;
|
|
126
|
+
}>;
|
|
127
|
+
_count?: {
|
|
128
|
+
followers?: number;
|
|
129
|
+
following?: number;
|
|
130
|
+
};
|
|
131
|
+
accountExpiresAfterInactivityDays?: number | null;
|
|
132
|
+
type?: "local" | "federated" | "agent" | "automated";
|
|
133
|
+
isFederated?: boolean;
|
|
134
|
+
isAgent?: boolean;
|
|
135
|
+
isAutomated?: boolean;
|
|
136
|
+
instance?: string;
|
|
137
|
+
federation?: {
|
|
138
|
+
actorUri?: string;
|
|
139
|
+
domain?: string;
|
|
140
|
+
actorId?: string;
|
|
141
|
+
};
|
|
142
|
+
automation?: {
|
|
143
|
+
ownerId?: string;
|
|
144
|
+
};
|
|
145
|
+
isManagedAccount?: boolean;
|
|
146
|
+
managedBy?: string;
|
|
147
|
+
};
|
|
148
|
+
error: null;
|
|
149
|
+
variables: UpdateAccountSettingsVariables;
|
|
150
|
+
isError: false;
|
|
151
|
+
isIdle: false;
|
|
152
|
+
isPending: false;
|
|
153
|
+
isSuccess: true;
|
|
154
|
+
status: "success";
|
|
155
|
+
reset: () => void;
|
|
156
|
+
context: {
|
|
157
|
+
previousUser: User | undefined;
|
|
158
|
+
} | undefined;
|
|
159
|
+
failureCount: number;
|
|
160
|
+
failureReason: Error | null;
|
|
161
|
+
isPaused: boolean;
|
|
162
|
+
submittedAt: number;
|
|
163
|
+
};
|
|
20
164
|
/**
|
|
21
165
|
* Update privacy settings with optimistic updates and authentication handling
|
|
22
166
|
*/
|
|
23
|
-
export declare const useUpdatePrivacySettings: () => import("@tanstack/react-query").UseMutationResult<
|
|
24
|
-
settings:
|
|
167
|
+
export declare const useUpdatePrivacySettings: () => import("@tanstack/react-query").UseMutationResult<PrivacySettings, Error, {
|
|
168
|
+
settings: Partial<PrivacySettings>;
|
|
25
169
|
userId?: string;
|
|
26
170
|
}, {
|
|
27
|
-
previousPrivacySettings:
|
|
171
|
+
previousPrivacySettings: PrivacySettings | undefined;
|
|
28
172
|
previousUser: User | undefined;
|
|
29
173
|
} | undefined>;
|
|
30
174
|
/** Uploaded file data structure from API */
|
|
@@ -57,7 +201,7 @@ interface UploadResult {
|
|
|
57
201
|
export declare const useUploadFile: () => import("@tanstack/react-query").UseMutationResult<UploadResult, Error, {
|
|
58
202
|
file: File;
|
|
59
203
|
visibility?: "private" | "public" | "unlisted";
|
|
60
|
-
metadata?: Record<string,
|
|
204
|
+
metadata?: Record<string, unknown>;
|
|
61
205
|
onProgress?: (progress: number) => void;
|
|
62
206
|
}, unknown>;
|
|
63
207
|
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App-Data Mutation Hooks
|
|
3
|
+
*
|
|
4
|
+
* Write side of the per-user JSON KV store. Both `useSetAppData` and
|
|
5
|
+
* `useDeleteAppData` apply optimistic updates against the two query keys
|
|
6
|
+
* that observe this data (`appDataQueryKeys.value` and the surrounding
|
|
7
|
+
* `appDataQueryKeys.namespace`) and roll back on error.
|
|
8
|
+
*
|
|
9
|
+
* When the underlying request fails because the endpoint isn't reachable
|
|
10
|
+
* (404 / network), the mutation still surfaces the error — write attempts
|
|
11
|
+
* are user-initiated and the caller may want to retry or fall back to
|
|
12
|
+
* local persistence. Reads are silent about missing endpoints; writes are
|
|
13
|
+
* not.
|
|
14
|
+
*/
|
|
15
|
+
interface SetAppDataVariables<T> {
|
|
16
|
+
namespace: string;
|
|
17
|
+
key: string;
|
|
18
|
+
value: T;
|
|
19
|
+
}
|
|
20
|
+
interface SetAppDataContext<T> {
|
|
21
|
+
previousValue: T | null | undefined;
|
|
22
|
+
previousNamespace: Record<string, T> | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Upsert a per-user JSON value. Returns the value the server confirmed it
|
|
26
|
+
* stored — typically identical to the input but consumers should prefer the
|
|
27
|
+
* returned value (the server is the source of truth).
|
|
28
|
+
*
|
|
29
|
+
* Applies optimistic updates against both the single-value query key and
|
|
30
|
+
* the surrounding namespace query key, then rolls back on error.
|
|
31
|
+
*/
|
|
32
|
+
export declare const useSetAppData: <T = unknown>() => import("@tanstack/react-query").UseMutationResult<T, Error, SetAppDataVariables<T>, SetAppDataContext<T>>;
|
|
33
|
+
interface DeleteAppDataVariables {
|
|
34
|
+
namespace: string;
|
|
35
|
+
key: string;
|
|
36
|
+
}
|
|
37
|
+
interface DeleteAppDataContext<T> {
|
|
38
|
+
previousValue: T | null | undefined;
|
|
39
|
+
previousNamespace: Record<string, T> | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Delete a per-user JSON value. Optimistically removes the entry from the
|
|
43
|
+
* single-value cache and from the surrounding namespace map, then rolls back
|
|
44
|
+
* on error.
|
|
45
|
+
*/
|
|
46
|
+
export declare const useDeleteAppData: <T = unknown>() => import("@tanstack/react-query").UseMutationResult<void, Error, DeleteAppDataVariables, DeleteAppDataContext<T>>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query keys + error utilities for `useAppData` hooks.
|
|
3
|
+
*
|
|
4
|
+
* Lives next to the hook file so consumers can import the keys directly
|
|
5
|
+
* when they need to imperatively invalidate a value (e.g. after a non-React
|
|
6
|
+
* write through `oxyServices.setAppData`).
|
|
7
|
+
*/
|
|
8
|
+
export declare const appDataQueryKeys: {
|
|
9
|
+
readonly all: readonly ["appData"];
|
|
10
|
+
readonly namespaces: () => readonly ["appData", "namespace"];
|
|
11
|
+
readonly namespace: (namespace: string) => readonly ["appData", "namespace", string];
|
|
12
|
+
readonly values: () => readonly ["appData", "value"];
|
|
13
|
+
readonly value: (namespace: string, key: string) => readonly ["appData", "value", string, string];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* True when `error` indicates the app-data endpoint isn't reachable — either
|
|
17
|
+
* because the API deployment doesn't have it yet (404) or there's a network
|
|
18
|
+
* failure with no response. We treat these as "no value stored" so consumers
|
|
19
|
+
* fall back to local persistence without surfacing a user-facing error.
|
|
20
|
+
*
|
|
21
|
+
* Anything else (401, 403, 500) propagates normally — those are real bugs
|
|
22
|
+
* the auth or retry pipeline needs to see.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isMissingAppDataEndpointError(error: unknown): boolean;
|
|
@@ -8,3 +8,5 @@ export { useUserProfile, useUserProfiles, useCurrentUser, useUserById, useUserBy
|
|
|
8
8
|
export { useSessions, useSession, useDeviceSessions, useUserDevices, useSecurityInfo, } from './useServicesQueries';
|
|
9
9
|
export { useSecurityActivity, useRecentSecurityActivity, } from './useSecurityQueries';
|
|
10
10
|
export { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from './queryKeys';
|
|
11
|
+
export { useAppData, useAppDataNamespace } from './useAppData';
|
|
12
|
+
export { appDataQueryKeys, isMissingAppDataEndpointError } from './appDataQueryKeys';
|
|
@@ -1,42 +1,46 @@
|
|
|
1
|
+
import type { User } from '@oxyhq/core';
|
|
1
2
|
/**
|
|
2
3
|
* Get user profile by session ID
|
|
3
4
|
*/
|
|
4
5
|
export declare const useUserProfile: (sessionId: string | null, options?: {
|
|
5
6
|
enabled?: boolean;
|
|
6
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
7
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<User>, Error>;
|
|
7
8
|
/**
|
|
8
9
|
* Get multiple user profiles by session IDs (batch query)
|
|
9
10
|
*/
|
|
10
11
|
export declare const useUserProfiles: (sessionIds: string[], options?: {
|
|
11
12
|
enabled?: boolean;
|
|
12
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
13
|
+
}) => import("@tanstack/react-query").UseQueryResult<User | null, Error>[];
|
|
13
14
|
/**
|
|
14
15
|
* Get current authenticated user
|
|
15
16
|
*/
|
|
16
17
|
export declare const useCurrentUser: (options?: {
|
|
17
18
|
enabled?: boolean;
|
|
18
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
19
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<User>, Error>;
|
|
19
20
|
/**
|
|
20
21
|
* Get user by ID
|
|
21
22
|
*/
|
|
22
23
|
export declare const useUserById: (userId: string | null, options?: {
|
|
23
24
|
enabled?: boolean;
|
|
24
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
25
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<User>, Error>;
|
|
25
26
|
/**
|
|
26
27
|
* Get user profile by username
|
|
27
28
|
*/
|
|
28
29
|
export declare const useUserByUsername: (username: string | null, options?: {
|
|
29
30
|
enabled?: boolean;
|
|
30
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
31
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<User>, Error>;
|
|
31
32
|
/**
|
|
32
33
|
* Batch get users by session IDs (optimized single API call)
|
|
33
34
|
*/
|
|
34
35
|
export declare const useUsersBySessions: (sessionIds: string[], options?: {
|
|
35
36
|
enabled?: boolean;
|
|
36
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
37
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<{
|
|
38
|
+
sessionId: string;
|
|
39
|
+
user: User | null;
|
|
40
|
+
}[]>, Error>;
|
|
37
41
|
/**
|
|
38
42
|
* Get privacy settings for a user
|
|
39
43
|
*/
|
|
40
44
|
export declare const usePrivacySettings: (userId?: string, options?: {
|
|
41
45
|
enabled?: boolean;
|
|
42
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
46
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@oxyhq/core").PrivacySettings>, Error>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App-Data Query Hooks
|
|
3
|
+
*
|
|
4
|
+
* Read side of the `/users/me/app-data/...` per-user JSON KV store. Gated on
|
|
5
|
+
* `isAuthenticated` — when signed out the query stays `enabled: false` and
|
|
6
|
+
* `data` is `null`, so consumers can fall back to localStorage without ever
|
|
7
|
+
* issuing a doomed request.
|
|
8
|
+
*
|
|
9
|
+
* Errors from the network (404 because the endpoint isn't deployed yet,
|
|
10
|
+
* 401 because the session lapsed, etc.) are not user-facing here. Hooks
|
|
11
|
+
* return `data: null` on error so the calling component renders the
|
|
12
|
+
* "nothing yet" state and the consuming app can quietly fall back to local
|
|
13
|
+
* persistence. Mutations still propagate errors so write attempts surface
|
|
14
|
+
* a toast — only reads are silent.
|
|
15
|
+
*/
|
|
16
|
+
import { type UseQueryResult } from '@tanstack/react-query';
|
|
17
|
+
interface AppDataQueryOptions {
|
|
18
|
+
/** Disable the query without unmounting the component. */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/** Override the default 1-minute stale time. */
|
|
21
|
+
staleTime?: number;
|
|
22
|
+
/** Override the default 30-minute gc time. */
|
|
23
|
+
gcTime?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read a single per-user JSON value.
|
|
27
|
+
*
|
|
28
|
+
* @param namespace - kebab/snake-case identifier (e.g. `"academy"`).
|
|
29
|
+
* @param key - kebab/snake-case identifier (e.g. course slug).
|
|
30
|
+
* @param options - optional `enabled`/`staleTime`/`gcTime` overrides.
|
|
31
|
+
*
|
|
32
|
+
* @returns A `useQuery` result with `data` of type `T | null`. The query
|
|
33
|
+
* stays disabled when the user is signed out; when enabled but the server
|
|
34
|
+
* has no stored value, `data` is `null`. Reads that fail because the
|
|
35
|
+
* endpoint isn't reachable also resolve to `null` so the consumer can
|
|
36
|
+
* fall back to local persistence.
|
|
37
|
+
*/
|
|
38
|
+
export declare const useAppData: <T = unknown>(namespace: string, key: string, options?: AppDataQueryOptions) => UseQueryResult<T | null, Error>;
|
|
39
|
+
/**
|
|
40
|
+
* Read every value in a namespace.
|
|
41
|
+
*
|
|
42
|
+
* @returns A `useQuery` result with `data` as a `Record<string, T>`. Empty
|
|
43
|
+
* object when the namespace contains nothing (or when fetching failed).
|
|
44
|
+
*/
|
|
45
|
+
export declare const useAppDataNamespace: <T = unknown>(namespace: string, options?: AppDataQueryOptions) => UseQueryResult<Record<string, T>, Error>;
|
|
46
|
+
export {};
|
|
@@ -7,8 +7,8 @@ export declare const useSecurityActivity: (options?: {
|
|
|
7
7
|
offset?: number;
|
|
8
8
|
eventType?: SecurityEventType;
|
|
9
9
|
enabled?: boolean;
|
|
10
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
10
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@oxyhq/core").SecurityActivityResponse>, Error>;
|
|
11
11
|
/**
|
|
12
12
|
* Get recent security activity (convenience hook)
|
|
13
13
|
*/
|
|
14
|
-
export declare const useRecentSecurityActivity: (limit?: number) => import("@tanstack/react-query").UseQueryResult<SecurityActivity[]
|
|
14
|
+
export declare const useRecentSecurityActivity: (limit?: number) => import("@tanstack/react-query").UseQueryResult<NoInfer<SecurityActivity[]>, Error>;
|
|
@@ -4,28 +4,30 @@ import type { ClientSession } from '@oxyhq/core';
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const useSessions: (userId?: string, options?: {
|
|
6
6
|
enabled?: boolean;
|
|
7
|
-
}) => import("@tanstack/react-query").UseQueryResult<ClientSession[]
|
|
7
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<ClientSession[]>, Error>;
|
|
8
8
|
/**
|
|
9
9
|
* Get specific session by ID
|
|
10
10
|
*/
|
|
11
11
|
export declare const useSession: (sessionId: string | null, options?: {
|
|
12
12
|
enabled?: boolean;
|
|
13
|
-
}) => import("@tanstack/react-query").UseQueryResult<ClientSession
|
|
13
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<ClientSession>, Error>;
|
|
14
14
|
/**
|
|
15
15
|
* Get device sessions for the current active session
|
|
16
16
|
*/
|
|
17
17
|
export declare const useDeviceSessions: (options?: {
|
|
18
18
|
enabled?: boolean;
|
|
19
|
-
}) => import("@tanstack/react-query").UseQueryResult<any
|
|
19
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<any[]>, Error>;
|
|
20
20
|
/**
|
|
21
21
|
* Get user devices
|
|
22
22
|
*/
|
|
23
23
|
export declare const useUserDevices: (options?: {
|
|
24
24
|
enabled?: boolean;
|
|
25
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
25
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<any[]>, Error>;
|
|
26
26
|
/**
|
|
27
27
|
* Get security information
|
|
28
28
|
*/
|
|
29
29
|
export declare const useSecurityInfo: (options?: {
|
|
30
30
|
enabled?: boolean;
|
|
31
|
-
}) => import("@tanstack/react-query").UseQueryResult<
|
|
31
|
+
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<{
|
|
32
|
+
recoveryEmail: string | null;
|
|
33
|
+
}>, Error>;
|
|
@@ -1,18 +1,32 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
-
import type { StorageInterface } from '../utils/storageHelpers';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Web QueryClient with offline-first defaults + localStorage persistence.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the persistence behaviour in `@oxyhq/services/queryClient` so
|
|
5
|
+
* web auth apps (FedCM, popup, redirect flows) survive a page reload with
|
|
6
|
+
* cached identity + paused mutations intact.
|
|
7
|
+
*
|
|
8
|
+
* Persistence is opt-in via `attachQueryPersistence(...)` so SSR callers
|
|
9
|
+
* (Next.js getServerSideProps, Vite SSR, tests) can create a stateless
|
|
10
|
+
* client without touching `window`.
|
|
5
11
|
*/
|
|
12
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
13
|
+
import { type PersistedClient } from '@tanstack/react-query-persist-client';
|
|
14
|
+
import type { StorageInterface } from '../utils/storageHelpers';
|
|
6
15
|
export declare const createPersistenceAdapter: (storage: StorageInterface) => {
|
|
7
|
-
persistClient: (client:
|
|
8
|
-
restoreClient: () => Promise<
|
|
16
|
+
persistClient: (client: unknown) => Promise<void>;
|
|
17
|
+
restoreClient: () => Promise<unknown>;
|
|
9
18
|
removeClient: () => Promise<void>;
|
|
10
19
|
};
|
|
20
|
+
export declare const createQueryClient: () => QueryClient;
|
|
21
|
+
export interface AttachPersistenceResult {
|
|
22
|
+
restored: Promise<void>;
|
|
23
|
+
unsubscribe: () => void;
|
|
24
|
+
}
|
|
11
25
|
/**
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Clear persisted query cache
|
|
26
|
+
* Wire `persistQueryClient` to browser `localStorage` (or a no-op when not
|
|
27
|
+
* in a browser). Returns the restore promise so consumers can `await` it
|
|
28
|
+
* before exposing the client to <Suspense> boundaries.
|
|
17
29
|
*/
|
|
30
|
+
export declare const attachQueryPersistence: (queryClient: QueryClient) => AttachPersistenceResult;
|
|
18
31
|
export declare const clearQueryCache: (storage: StorageInterface) => Promise<void>;
|
|
32
|
+
export type { PersistedClient };
|
|
@@ -24,7 +24,7 @@ export declare const useAssets: () => {
|
|
|
24
24
|
getAsset: (assetId: string) => Promise<Asset>;
|
|
25
25
|
deleteAsset: (assetId: string, force?: boolean) => Promise<void>;
|
|
26
26
|
restore: (assetId: string) => Promise<void>;
|
|
27
|
-
getVariants: (assetId: string) => Promise<
|
|
27
|
+
getVariants: (assetId: string) => Promise<import("@oxyhq/core").AssetVariant[]>;
|
|
28
28
|
getAssetsByApp: (app: string) => Asset[];
|
|
29
29
|
getAssetsByEntity: (app: string, entityType: string, entityId: string) => Asset[];
|
|
30
30
|
getAssetUsageCount: (assetId: string) => number;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { OxyServices } from '@oxyhq/core';
|
|
2
|
-
export declare const setOxyFileUrlInstance: (instance: OxyServices) => void;
|
|
1
|
+
import type { OxyServices } from '@oxyhq/core';
|
|
3
2
|
export interface UseFileDownloadUrlOptions {
|
|
4
3
|
variant?: string;
|
|
5
4
|
expiresIn?: number;
|
|
@@ -12,10 +11,7 @@ export interface UseFileDownloadUrlResult {
|
|
|
12
11
|
/**
|
|
13
12
|
* Hook to resolve a file's download URL asynchronously.
|
|
14
13
|
*
|
|
15
|
-
* Prefers the provided `oxyServices` instance, falls back to the module-level
|
|
16
|
-
* singleton set via `setOxyFileUrlInstance`.
|
|
17
|
-
*
|
|
18
14
|
* Uses `getFileDownloadUrlAsync` first, falling back to the synchronous
|
|
19
15
|
* `getFileDownloadUrl` if the async call fails.
|
|
20
16
|
*/
|
|
21
|
-
export declare const useFileDownloadUrl: (
|
|
17
|
+
export declare const useFileDownloadUrl: (oxyServices: OxyServices | null | undefined, fileId: string | null | undefined, options?: UseFileDownloadUrlOptions) => UseFileDownloadUrlResult;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -29,15 +29,15 @@ export { useAssetStore, useAssets as useAssetsStore, useAsset, useUploadProgress
|
|
|
29
29
|
export { useAccountStore, useAccounts, useAccountLoading, useAccountError, useAccountLoadingSession, } from './stores/accountStore';
|
|
30
30
|
export type { QuickAccount } from './stores/accountStore';
|
|
31
31
|
export { useFollowStore, } from './stores/followStore';
|
|
32
|
-
export { useUserProfile, useUserProfiles, useCurrentUser, useUserById, useUserByUsername, useUsersBySessions, usePrivacySettings, useSessions, useSession, useDeviceSessions, useUserDevices, useSecurityInfo, useSecurityActivity, useRecentSecurityActivity, } from './hooks/queries';
|
|
33
|
-
export { useUpdateProfile, useUploadAvatar, useUpdateAccountSettings, useUpdatePrivacySettings, useUploadFile, useSwitchSession, useLogoutSession, useLogoutAll, useUpdateDeviceName, useRemoveDevice, } from './hooks/mutations';
|
|
32
|
+
export { useUserProfile, useUserProfiles, useCurrentUser, useUserById, useUserByUsername, useUsersBySessions, usePrivacySettings, useSessions, useSession, useDeviceSessions, useUserDevices, useSecurityInfo, useSecurityActivity, useRecentSecurityActivity, useAppData, useAppDataNamespace, appDataQueryKeys, isMissingAppDataEndpointError, } from './hooks/queries';
|
|
33
|
+
export { useUpdateProfile, useUploadAvatar, useUpdateAccountSettings, useUpdatePrivacySettings, useUploadFile, useSwitchSession, useLogoutSession, useLogoutAll, useUpdateDeviceName, useRemoveDevice, useSetAppData, useDeleteAppData, } from './hooks/mutations';
|
|
34
34
|
export { createProfileMutation, createGenericMutation, } from './hooks/mutations/mutationFactory';
|
|
35
35
|
export type { ProfileMutationConfig, GenericMutationConfig, } from './hooks/mutations/mutationFactory';
|
|
36
36
|
export { useWebSSO, isWebBrowser } from './hooks/useWebSSO';
|
|
37
37
|
export { useSessionSocket } from './hooks/useSessionSocket';
|
|
38
38
|
export type { UseSessionSocketOptions } from './hooks/useSessionSocket';
|
|
39
39
|
export { useAssets, setOxyAssetInstance } from './hooks/useAssets';
|
|
40
|
-
export { useFileDownloadUrl
|
|
40
|
+
export { useFileDownloadUrl } from './hooks/useFileDownloadUrl';
|
|
41
41
|
export { useFollow, useFollowerCounts } from './hooks/useFollow';
|
|
42
42
|
export { useFileFiltering } from './hooks/useFileFiltering';
|
|
43
43
|
export type { ViewMode, SortBy, SortOrder } from './hooks/useFileFiltering';
|
|
@@ -45,7 +45,9 @@ export interface SessionValidationResult {
|
|
|
45
45
|
*/
|
|
46
46
|
export declare const mapSessionsToClient: (sessions: DeviceSession[], fallbackDeviceId?: string, fallbackUserId?: string) => ClientSession[];
|
|
47
47
|
/**
|
|
48
|
-
* Fetch device sessions
|
|
48
|
+
* Fetch device sessions, falling back to the per-user session endpoint
|
|
49
|
+
* if the device endpoint is unavailable (older API versions or disabled
|
|
50
|
+
* device-grouping feature flag).
|
|
49
51
|
*
|
|
50
52
|
* @param oxyServices - Oxy service instance
|
|
51
53
|
* @param sessionId - Session identifier to fetch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/auth",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "OxyHQ Web Authentication SDK — headless auth with React hooks for Next.js, Vite, and web apps",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -50,10 +50,28 @@
|
|
|
50
50
|
"build:types": "tsc -p tsconfig.types.json",
|
|
51
51
|
"clean": "rm -rf dist",
|
|
52
52
|
"typescript": "tsc --noEmit",
|
|
53
|
-
"lint": "biome lint --error-on-warnings ./src"
|
|
53
|
+
"lint": "biome lint --error-on-warnings ./src",
|
|
54
|
+
"test": "jest --passWithNoTests",
|
|
55
|
+
"release": "rm -rf dist && bun run build && release-it"
|
|
56
|
+
},
|
|
57
|
+
"release-it": {
|
|
58
|
+
"git": {
|
|
59
|
+
"tagName": "@oxyhq/auth@${version}",
|
|
60
|
+
"tagAnnotation": "Release @oxyhq/auth@${version}",
|
|
61
|
+
"commitMessage": "chore(auth-sdk): release @oxyhq/auth@${version}"
|
|
62
|
+
},
|
|
63
|
+
"github": {
|
|
64
|
+
"release": true,
|
|
65
|
+
"releaseName": "@oxyhq/auth@${version}"
|
|
66
|
+
},
|
|
67
|
+
"npm": {
|
|
68
|
+
"publish": true
|
|
69
|
+
}
|
|
54
70
|
},
|
|
55
71
|
"dependencies": {
|
|
56
|
-
"@tanstack/
|
|
72
|
+
"@tanstack/query-sync-storage-persister": "^5.100",
|
|
73
|
+
"@tanstack/react-query": "^5.100",
|
|
74
|
+
"@tanstack/react-query-persist-client": "^5.100",
|
|
57
75
|
"sonner": "^2.0.4",
|
|
58
76
|
"zustand": "^5.0.9"
|
|
59
77
|
},
|
|
@@ -71,6 +89,7 @@
|
|
|
71
89
|
"@biomejs/biome": "^1.9.4",
|
|
72
90
|
"@types/react": "^19.2.0",
|
|
73
91
|
"@types/node": "^20.19.9",
|
|
92
|
+
"release-it": "^19.0.6",
|
|
74
93
|
"typescript": "^5.9.2"
|
|
75
94
|
}
|
|
76
95
|
}
|
package/src/WebOxyProvider.tsx
CHANGED
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
ClientSession,
|
|
29
29
|
} from '@oxyhq/core';
|
|
30
30
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
31
|
-
import { createQueryClient } from './hooks/queryClient';
|
|
31
|
+
import { attachQueryPersistence, createQueryClient } from './hooks/queryClient';
|
|
32
32
|
|
|
33
33
|
export interface WebAuthState {
|
|
34
34
|
user: User | null;
|
|
@@ -101,6 +101,29 @@ export function WebOxyProvider({
|
|
|
101
101
|
const [authManager] = useState(() => createAuthManager(oxyServices, { autoRefresh: true }));
|
|
102
102
|
const [queryClient] = useState(() => createQueryClient());
|
|
103
103
|
|
|
104
|
+
// Block first render until the persisted localStorage cache has been
|
|
105
|
+
// restored — mirrors the RN OxyProvider pattern. Without this gate the
|
|
106
|
+
// first paint observes an empty cache and any consumer reading
|
|
107
|
+
// `getQueryData(...)` synchronously (or using `placeholderData: 'previous'`
|
|
108
|
+
// gating) misses the persisted blob.
|
|
109
|
+
//
|
|
110
|
+
// Persistence is attached inside the same effect so we can hold a
|
|
111
|
+
// reference to the `restored` promise and only flip `isRestoring` to
|
|
112
|
+
// false once it settles (success OR failure). Detach on unmount so HMR
|
|
113
|
+
// doesn't leak subscriptions.
|
|
114
|
+
const [isRestoring, setIsRestoring] = useState(true);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
let mounted = true;
|
|
117
|
+
const { restored, unsubscribe } = attachQueryPersistence(queryClient);
|
|
118
|
+
restored.finally(() => {
|
|
119
|
+
if (mounted) setIsRestoring(false);
|
|
120
|
+
});
|
|
121
|
+
return () => {
|
|
122
|
+
mounted = false;
|
|
123
|
+
unsubscribe();
|
|
124
|
+
};
|
|
125
|
+
}, [queryClient]);
|
|
126
|
+
|
|
104
127
|
// Auth state
|
|
105
128
|
const [user, setUser] = useState<User | null>(null);
|
|
106
129
|
const [isLoading, setIsLoading] = useState(!skipAutoCheck);
|
|
@@ -334,6 +357,21 @@ export function WebOxyProvider({
|
|
|
334
357
|
signOut, isFedCMSupported, switchSession, clearSessionState,
|
|
335
358
|
]);
|
|
336
359
|
|
|
360
|
+
// Mirror the RN OxyProvider pattern: don't expose the QueryClient (or
|
|
361
|
+
// mount children) until the persisted cache has been restored. On the
|
|
362
|
+
// web this prevents the first paint from observing an empty
|
|
363
|
+
// localStorage-backed cache, which would otherwise force every
|
|
364
|
+
// identity/session/auth query to refetch from the network even when a
|
|
365
|
+
// fresh blob was available on disk.
|
|
366
|
+
//
|
|
367
|
+
// The restored promise is wired with `.finally(...)` upstream, so this
|
|
368
|
+
// unblocks on both success and failure within typically <50ms (sync
|
|
369
|
+
// localStorage read + JSON.parse). A safety net is unnecessary: the
|
|
370
|
+
// restore promise always settles synchronously after one microtask.
|
|
371
|
+
if (isRestoring) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
337
375
|
return (
|
|
338
376
|
<QueryClientProvider client={queryClient}>
|
|
339
377
|
<WebOxyContext.Provider value={contextValue}>
|