@oxyhq/services 6.10.2 → 6.10.4

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.
Files changed (76) hide show
  1. package/lib/commonjs/index.js +32 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +22 -9
  4. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/queries/index.js +31 -0
  6. package/lib/commonjs/ui/hooks/queries/index.js.map +1 -1
  7. package/lib/commonjs/ui/hooks/queries/paymentTypes.js +2 -0
  8. package/lib/commonjs/ui/hooks/queries/paymentTypes.js.map +1 -0
  9. package/lib/commonjs/ui/hooks/queries/queryKeys.js +13 -0
  10. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/queries/usePaymentQueries.js +140 -0
  12. package/lib/commonjs/ui/hooks/queries/usePaymentQueries.js.map +1 -0
  13. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +26 -1
  14. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
  15. package/lib/commonjs/ui/hooks/queryClient.js +1 -1
  16. package/lib/commonjs/ui/hooks/queryClient.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/useWebSSO.js +47 -2
  18. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
  19. package/lib/module/index.js +3 -1
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/module/ui/context/hooks/useAuthOperations.js +22 -9
  22. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  23. package/lib/module/ui/hooks/queries/index.js +7 -2
  24. package/lib/module/ui/hooks/queries/index.js.map +1 -1
  25. package/lib/module/ui/hooks/queries/paymentTypes.js +2 -0
  26. package/lib/module/ui/hooks/queries/paymentTypes.js.map +1 -0
  27. package/lib/module/ui/hooks/queries/queryKeys.js +13 -0
  28. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
  29. package/lib/module/ui/hooks/queries/usePaymentQueries.js +132 -0
  30. package/lib/module/ui/hooks/queries/usePaymentQueries.js.map +1 -0
  31. package/lib/module/ui/hooks/queries/useServicesQueries.js +24 -0
  32. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
  33. package/lib/module/ui/hooks/queryClient.js +1 -1
  34. package/lib/module/ui/hooks/queryClient.js.map +1 -1
  35. package/lib/module/ui/hooks/useWebSSO.js +47 -2
  36. package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
  37. package/lib/typescript/commonjs/index.d.ts +2 -1
  38. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/ui/hooks/queries/index.d.ts +3 -1
  41. package/lib/typescript/commonjs/ui/hooks/queries/index.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/ui/hooks/queries/paymentTypes.d.ts +137 -0
  43. package/lib/typescript/commonjs/ui/hooks/queries/paymentTypes.d.ts.map +1 -0
  44. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts +11 -0
  45. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/ui/hooks/queries/usePaymentQueries.d.ts +63 -0
  47. package/lib/typescript/commonjs/ui/hooks/queries/usePaymentQueries.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +11 -1
  49. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/ui/hooks/queryClient.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
  52. package/lib/typescript/module/index.d.ts +2 -1
  53. package/lib/typescript/module/index.d.ts.map +1 -1
  54. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  55. package/lib/typescript/module/ui/hooks/queries/index.d.ts +3 -1
  56. package/lib/typescript/module/ui/hooks/queries/index.d.ts.map +1 -1
  57. package/lib/typescript/module/ui/hooks/queries/paymentTypes.d.ts +137 -0
  58. package/lib/typescript/module/ui/hooks/queries/paymentTypes.d.ts.map +1 -0
  59. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts +11 -0
  60. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  61. package/lib/typescript/module/ui/hooks/queries/usePaymentQueries.d.ts +63 -0
  62. package/lib/typescript/module/ui/hooks/queries/usePaymentQueries.d.ts.map +1 -0
  63. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +11 -1
  64. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  65. package/lib/typescript/module/ui/hooks/queryClient.d.ts.map +1 -1
  66. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
  67. package/package.json +1 -1
  68. package/src/index.ts +9 -0
  69. package/src/ui/context/hooks/useAuthOperations.ts +22 -9
  70. package/src/ui/hooks/queries/index.ts +25 -1
  71. package/src/ui/hooks/queries/paymentTypes.ts +141 -0
  72. package/src/ui/hooks/queries/queryKeys.ts +26 -0
  73. package/src/ui/hooks/queries/usePaymentQueries.ts +136 -0
  74. package/src/ui/hooks/queries/useServicesQueries.ts +26 -1
  75. package/src/ui/hooks/queryClient.ts +1 -0
  76. package/src/ui/hooks/useWebSSO.ts +48 -2
@@ -1 +1 @@
1
- {"version":3,"file":"useServicesQueries.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/queries/useServicesQueries.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,oFAqB3E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,GAAG,IAAI,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,kFA6BnF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,0EAgBhE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,0EAgB7D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;;UAY9D,CAAC"}
1
+ {"version":3,"file":"useServicesQueries.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/queries/useServicesQueries.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK9E;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,oFAqB3E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,GAAG,IAAI,EAAE,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,kFA6BnF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,0EAgBhE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,0EAgB7D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;;UAY9D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,gGAgBrE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"queryClient.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/queryClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,WAAW,EAA4C,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,sCAAsC,CAAC;AAG9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAmEhE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,QAAO,WA6CpC,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GACjC,aAAa,WAAW,EACxB,SAAS,gBAAgB,GAAG,IAAI,GAAG,SAAS,KAC3C,uBA+BF,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAa7E,CAAC;AAEF;;GAEG;AACH,YAAY,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"queryClient.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/queryClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,WAAW,EAA4C,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,sCAAsC,CAAC;AAG9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAoEhE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,QAAO,WA6CpC,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GACjC,aAAa,WAAW,EACxB,SAAS,gBAAgB,GAAG,IAAI,GAAG,SAAS,KAC3C,uBA+BF,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAa7E,CAAC;AAEF;;GAEG;AACH,YAAY,EAAE,eAAe,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,2DAA2D;IAC3D,eAAe,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC5D,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CAwGpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,2DAA2D;IAC3D,eAAe,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC5D,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAgCD;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CAwHpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "6.10.2",
3
+ "version": "6.10.4",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
package/src/index.ts CHANGED
@@ -63,7 +63,16 @@ export {
63
63
  useUserById, useUserByUsername, useUsersBySessions,
64
64
  usePrivacySettings, useSessions, useSession,
65
65
  useDeviceSessions, useUserDevices, useSecurityInfo,
66
+ useAccountStorageUsage,
66
67
  useSecurityActivity, useRecentSecurityActivity, useInfiniteSecurityActivity,
68
+ useUserSubscription, useUserPayments, useUserWallet, useUserWalletTransactions,
69
+ } from './ui/hooks/queries';
70
+
71
+ // Payment / wallet / subscription domain types
72
+ export type {
73
+ Subscription, SubscriptionPlan, SubscriptionStatus, SubscriptionFeatures,
74
+ Payment, Wallet, WalletTransaction, WalletTransactionType,
75
+ WalletTransactionStatus, WalletPagination, WalletTransactionsResponse,
67
76
  } from './ui/hooks/queries';
68
77
 
69
78
  // Mutation hooks
@@ -189,16 +189,29 @@ export const useAuthOperations = ({
189
189
  deviceFingerprint,
190
190
  );
191
191
 
192
- // Get token for the session
193
- try {
194
- await oxyServices.getTokenBySession(sessionResponse.sessionId);
195
- } catch (tokenError: unknown) {
196
- const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
197
- const status = isErrorWithCodeOrStatus(tokenError) ? tokenError.status : undefined;
198
- if (status === 404 || errorMessage.includes('404')) {
199
- throw new Error(`Session was created but token could not be retrieved. Session ID: ${sessionResponse.sessionId.substring(0, 8)}...`);
192
+ // Plant the first access token. `/auth/verify` already returns the
193
+ // freshly-minted access token in its response body, so use it
194
+ // directly. We must NOT fall back to `GET /session/token/:sessionId`
195
+ // here for a brand-new sign-in: that route is bearer-protected (C1
196
+ // hardening) and the client has no bearer yet, so calling it without
197
+ // a token returns 401 "Invalid or missing authorization header" —
198
+ // which previously broke the entire new-identity onboarding flow.
199
+ // Only when the verify response somehow omits the token do we fall
200
+ // back, and by then `setTokens` has not run so a 401 there is a real
201
+ // server fault worth surfacing.
202
+ if (sessionResponse.accessToken) {
203
+ oxyServices.setTokens(sessionResponse.accessToken, sessionResponse.refreshToken ?? '');
204
+ } else {
205
+ try {
206
+ await oxyServices.getTokenBySession(sessionResponse.sessionId);
207
+ } catch (tokenError: unknown) {
208
+ const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
209
+ const status = isErrorWithCodeOrStatus(tokenError) ? tokenError.status : undefined;
210
+ if (status === 404 || errorMessage.includes('404')) {
211
+ throw new Error(`Session was created but token could not be retrieved. Session ID: ${sessionResponse.sessionId.substring(0, 8)}...`);
212
+ }
213
+ throw tokenError;
200
214
  }
201
- throw tokenError;
202
215
  }
203
216
 
204
217
  // Get full user data
@@ -16,13 +16,14 @@ export {
16
16
  usePrivacySettings,
17
17
  } from './useAccountQueries';
18
18
 
19
- // Service query hooks (sessions, devices, security)
19
+ // Service query hooks (sessions, devices, security, storage)
20
20
  export {
21
21
  useSessions,
22
22
  useSession,
23
23
  useDeviceSessions,
24
24
  useUserDevices,
25
25
  useSecurityInfo,
26
+ useAccountStorageUsage,
26
27
  } from './useServicesQueries';
27
28
 
28
29
  // Security activity query hooks
@@ -32,6 +33,29 @@ export {
32
33
  useInfiniteSecurityActivity,
33
34
  } from './useSecurityQueries';
34
35
 
36
+ // Payment / wallet / subscription query hooks
37
+ export {
38
+ useUserSubscription,
39
+ useUserPayments,
40
+ useUserWallet,
41
+ useUserWalletTransactions,
42
+ } from './usePaymentQueries';
43
+
44
+ // Payment / wallet / subscription domain types
45
+ export type {
46
+ Subscription,
47
+ SubscriptionPlan,
48
+ SubscriptionStatus,
49
+ SubscriptionFeatures,
50
+ Payment,
51
+ Wallet,
52
+ WalletTransaction,
53
+ WalletTransactionType,
54
+ WalletTransactionStatus,
55
+ WalletPagination,
56
+ WalletTransactionsResponse,
57
+ } from './paymentTypes';
58
+
35
59
  // Query keys and invalidation helpers (for advanced usage)
36
60
  export { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from './queryKeys';
37
61
 
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Payment / wallet / subscription domain types.
3
+ *
4
+ * These model the JSON shapes the Oxy API actually returns for the
5
+ * payment-related read endpoints, so the corresponding query hooks
6
+ * (`useUserSubscription`, `useUserPayments`, `useUserWallet`,
7
+ * `useUserWalletTransactions`) expose precise types instead of `any`.
8
+ *
9
+ * Source of truth (API):
10
+ * - Subscription: GET /subscription/:userId -> `Subscription` model JSON,
11
+ * or the fallback `{ plan: 'basic' }` when none exists.
12
+ * - Payments: GET /payments/user -> `Payment[]` (sendSuccess).
13
+ * - Wallet: GET /wallet/:userId -> `Wallet` (sendSuccess).
14
+ * - Transactions: GET /wallet/transactions/:userId
15
+ * -> `WalletTransactionsResponse` (sendPaginated envelope).
16
+ *
17
+ * All `Date` columns are serialized as ISO-8601 strings over the wire.
18
+ *
19
+ * Note: `@oxyhq/services` deliberately does NOT re-export these from
20
+ * `@oxyhq/core`. They live here because they describe the return shape of
21
+ * the services-layer query hooks; consumers that need core domain types
22
+ * import those directly from `@oxyhq/core`.
23
+ */
24
+
25
+ /** Subscription tier. Mirrors the `plan` enum on the API `Subscription` model. */
26
+ export type SubscriptionPlan = 'basic' | 'pro' | 'business';
27
+
28
+ /** Lifecycle state of a subscription. */
29
+ export type SubscriptionStatus = 'active' | 'canceled' | 'expired';
30
+
31
+ /** Feature flags toggled by the active subscription plan. */
32
+ export interface SubscriptionFeatures {
33
+ analytics: boolean;
34
+ premiumBadge: boolean;
35
+ unlimitedFollowing: boolean;
36
+ higherUploadLimits: boolean;
37
+ promotedPosts: boolean;
38
+ businessTools: boolean;
39
+ }
40
+
41
+ /**
42
+ * A user's subscription.
43
+ *
44
+ * When a user has never subscribed, the API returns the minimal fallback
45
+ * `{ plan: 'basic' }`; every other field is therefore optional so the type
46
+ * covers both the persisted document and the fallback.
47
+ */
48
+ export interface Subscription {
49
+ plan: SubscriptionPlan;
50
+ status?: SubscriptionStatus;
51
+ /** Account this subscription belongs to (absent on the basic fallback). */
52
+ userId?: string;
53
+ /** ISO-8601 timestamp the current period started. */
54
+ startDate?: string;
55
+ /** ISO-8601 timestamp the current period ends / renews. */
56
+ endDate?: string;
57
+ autoRenew?: boolean;
58
+ paymentMethod?: string;
59
+ latestInvoice?: string;
60
+ features?: SubscriptionFeatures;
61
+ /** ISO-8601 creation timestamp. */
62
+ createdAt?: string;
63
+ /** ISO-8601 last-update timestamp. */
64
+ updatedAt?: string;
65
+ }
66
+
67
+ /**
68
+ * A billing / payment history entry, derived from `deposit` and `purchase`
69
+ * transactions on the API side (`GET /payments/user`).
70
+ */
71
+ export interface Payment {
72
+ id: string;
73
+ userId: string;
74
+ type: string;
75
+ amount: number;
76
+ status: string;
77
+ description?: string;
78
+ itemId?: string;
79
+ itemType?: string;
80
+ /** ISO-8601 timestamp the payment was created (maps to `createdAt`). */
81
+ timestamp?: string;
82
+ /** ISO-8601 timestamp the payment settled, when applicable. */
83
+ completedAt?: string;
84
+ }
85
+
86
+ /** FairCoin wallet balance for a user (`GET /wallet/:userId`). */
87
+ export interface Wallet {
88
+ userId: string;
89
+ balance: number;
90
+ /** On-chain withdrawal address, `null` until one is set. */
91
+ address: string | null;
92
+ }
93
+
94
+ /** Direction/category of a wallet transaction. */
95
+ export type WalletTransactionType = 'deposit' | 'withdrawal' | 'transfer' | 'purchase';
96
+
97
+ /** Settlement state of a wallet transaction. */
98
+ export type WalletTransactionStatus = 'pending' | 'completed' | 'failed' | 'cancelled';
99
+
100
+ /**
101
+ * A single wallet ledger entry returned by the transaction-history endpoint.
102
+ *
103
+ * The populated `userId` / `recipientId` may arrive either as a bare id
104
+ * string or as a `{ _id, username }` object depending on Mongoose
105
+ * population, so both shapes are modelled.
106
+ */
107
+ export interface WalletTransaction {
108
+ id: string;
109
+ userId: string | { _id: string; username?: string };
110
+ type: WalletTransactionType;
111
+ amount: number;
112
+ status: WalletTransactionStatus;
113
+ description?: string;
114
+ recipientId?: string | { _id: string; username?: string } | null;
115
+ itemId?: string;
116
+ itemType?: string;
117
+ /** ISO-8601 creation timestamp (maps to `createdAt`). */
118
+ timestamp?: string;
119
+ /** ISO-8601 settlement timestamp, when applicable. */
120
+ completedAt?: string;
121
+ }
122
+
123
+ /** Pagination metadata attached to a paginated list response. */
124
+ export interface WalletPagination {
125
+ total: number;
126
+ limit: number;
127
+ offset: number;
128
+ hasMore: boolean;
129
+ }
130
+
131
+ /**
132
+ * Paginated envelope returned by `GET /wallet/transactions/:userId`.
133
+ *
134
+ * The HTTP layer preserves the `{ data, pagination }` shape for paginated
135
+ * responses (it only unwraps the plain `{ data }` success envelope), so
136
+ * callers receive the list under `data`.
137
+ */
138
+ export interface WalletTransactionsResponse {
139
+ data: WalletTransaction[];
140
+ pagination: WalletPagination;
141
+ }
@@ -65,6 +65,32 @@ export const queryKeys = {
65
65
  infinite: (limit: number, eventType?: string) =>
66
66
  [...queryKeys.security.all, 'infinite', limit, eventType] as const,
67
67
  },
68
+
69
+ // Storage usage queries
70
+ storage: {
71
+ all: ['storage'] as const,
72
+ usage: () => [...queryKeys.storage.all, 'usage'] as const,
73
+ },
74
+
75
+ // Payment / wallet / subscription queries
76
+ payments: {
77
+ all: ['payments'] as const,
78
+ subscription: (userId?: string) =>
79
+ [...queryKeys.payments.all, 'subscription', userId || 'current'] as const,
80
+ history: (userId?: string) =>
81
+ [...queryKeys.payments.all, 'history', userId || 'current'] as const,
82
+ wallet: (userId?: string) =>
83
+ [...queryKeys.payments.all, 'wallet', userId || 'current'] as const,
84
+ walletTransactions: (limit?: number, offset?: number, userId?: string) =>
85
+ [
86
+ ...queryKeys.payments.all,
87
+ 'wallet',
88
+ userId || 'current',
89
+ 'transactions',
90
+ limit,
91
+ offset,
92
+ ] as const,
93
+ },
68
94
  } as const;
69
95
 
70
96
  /**
@@ -0,0 +1,136 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { authenticatedApiCall } from '@oxyhq/core';
3
+ import { queryKeys } from './queryKeys';
4
+ import { useOxy } from '../../context/OxyContext';
5
+ import type {
6
+ Subscription,
7
+ Payment,
8
+ Wallet,
9
+ WalletTransactionsResponse,
10
+ } from './paymentTypes';
11
+
12
+ /**
13
+ * Payment / wallet / subscription query hooks.
14
+ *
15
+ * These replace the imperative `oxyServices.*` + `Promise.allSettled` +
16
+ * `useEffect` pattern used in app screens. Each hook:
17
+ * - is gated on `isAuthenticated` (the underlying SDK methods read the
18
+ * current user id off the access token and throw when unauthenticated),
19
+ * - routes through `authenticatedApiCall` so a stale token is refreshed and
20
+ * auth errors are handled consistently (matches `useUserDevices` /
21
+ * `usePrivacySettings`),
22
+ * - keeps its `queryFn` pure — no store writes, no side effects.
23
+ *
24
+ * The wrapped SDK methods (`@oxyhq/core` payment mixin) return `any`; the
25
+ * generic on `authenticatedApiCall<T>` pins the resolved value to the
26
+ * precise domain type so consumers get full typing.
27
+ */
28
+
29
+ /**
30
+ * Get the current user's subscription.
31
+ *
32
+ * Wraps `oxyServices.getCurrentUserSubscription()`
33
+ * (`GET /subscription/:userId`). Returns the persisted subscription or the
34
+ * API's `{ plan: 'basic' }` fallback when the user has never subscribed.
35
+ */
36
+ export const useUserSubscription = (options?: { enabled?: boolean }) => {
37
+ const { oxyServices, isAuthenticated, activeSessionId } = useOxy();
38
+
39
+ return useQuery({
40
+ queryKey: queryKeys.payments.subscription(),
41
+ queryFn: async () => {
42
+ return authenticatedApiCall<Subscription>(
43
+ oxyServices,
44
+ activeSessionId,
45
+ () => oxyServices.getCurrentUserSubscription(),
46
+ );
47
+ },
48
+ enabled: (options?.enabled !== false) && isAuthenticated,
49
+ // Subscription state changes rarely; tolerate a longer fresh window.
50
+ staleTime: 5 * 60 * 1000, // 5 minutes
51
+ gcTime: 30 * 60 * 1000, // 30 minutes
52
+ });
53
+ };
54
+
55
+ /**
56
+ * Get the current user's payment / billing history.
57
+ *
58
+ * Wraps `oxyServices.getUserPayments()` (`GET /payments/user`), which
59
+ * returns the user's `deposit` and `purchase` transactions newest-first.
60
+ */
61
+ export const useUserPayments = (options?: { enabled?: boolean }) => {
62
+ const { oxyServices, isAuthenticated, activeSessionId } = useOxy();
63
+
64
+ return useQuery({
65
+ queryKey: queryKeys.payments.history(),
66
+ queryFn: async () => {
67
+ return authenticatedApiCall<Payment[]>(
68
+ oxyServices,
69
+ activeSessionId,
70
+ () => oxyServices.getUserPayments(),
71
+ );
72
+ },
73
+ enabled: (options?.enabled !== false) && isAuthenticated,
74
+ // Billing history is append-mostly; a short fresh window is fine.
75
+ staleTime: 2 * 60 * 1000, // 2 minutes
76
+ gcTime: 30 * 60 * 1000, // 30 minutes
77
+ });
78
+ };
79
+
80
+ /**
81
+ * Get the current user's FairCoin wallet balance.
82
+ *
83
+ * Wraps `oxyServices.getCurrentUserWallet()` (`GET /wallet/:userId`).
84
+ * Balance changes frequently, so the fresh window is intentionally short.
85
+ */
86
+ export const useUserWallet = (options?: { enabled?: boolean }) => {
87
+ const { oxyServices, isAuthenticated, activeSessionId } = useOxy();
88
+
89
+ return useQuery({
90
+ queryKey: queryKeys.payments.wallet(),
91
+ queryFn: async () => {
92
+ return authenticatedApiCall<Wallet>(
93
+ oxyServices,
94
+ activeSessionId,
95
+ () => oxyServices.getCurrentUserWallet(),
96
+ );
97
+ },
98
+ enabled: (options?.enabled !== false) && isAuthenticated,
99
+ staleTime: 60 * 1000, // 1 minute (balance moves often)
100
+ gcTime: 10 * 60 * 1000, // 10 minutes
101
+ });
102
+ };
103
+
104
+ /**
105
+ * Get the current user's wallet transaction history (paginated).
106
+ *
107
+ * Wraps `oxyServices.getCurrentUserWalletTransactions(options)`
108
+ * (`GET /wallet/transactions/:userId`). The API responds with a
109
+ * `{ data, pagination }` envelope, preserved here as
110
+ * `WalletTransactionsResponse`.
111
+ *
112
+ * @param params - Optional `limit` / `offset` pagination controls. These are
113
+ * part of the query key, so distinct pages cache independently.
114
+ */
115
+ export const useUserWalletTransactions = (
116
+ params?: { limit?: number; offset?: number },
117
+ options?: { enabled?: boolean },
118
+ ) => {
119
+ const { oxyServices, isAuthenticated, activeSessionId } = useOxy();
120
+ const limit = params?.limit;
121
+ const offset = params?.offset;
122
+
123
+ return useQuery({
124
+ queryKey: queryKeys.payments.walletTransactions(limit, offset),
125
+ queryFn: async () => {
126
+ return authenticatedApiCall<WalletTransactionsResponse>(
127
+ oxyServices,
128
+ activeSessionId,
129
+ () => oxyServices.getCurrentUserWalletTransactions({ limit, offset }),
130
+ );
131
+ },
132
+ enabled: (options?.enabled !== false) && isAuthenticated,
133
+ staleTime: 60 * 1000, // 1 minute (ledger updates frequently)
134
+ gcTime: 10 * 60 * 1000, // 10 minutes
135
+ });
136
+ };
@@ -1,6 +1,6 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
2
  import { authenticatedApiCall } from '@oxyhq/core';
3
- import type { ClientSession } from '@oxyhq/core';
3
+ import type { AccountStorageUsageResponse, ClientSession } from '@oxyhq/core';
4
4
  import { queryKeys } from './queryKeys';
5
5
  import { useOxy } from '../../context/OxyContext';
6
6
  import { fetchSessionsWithFallback, mapSessionsToClient } from '../../utils/sessionHelpers';
@@ -124,3 +124,28 @@ export const useSecurityInfo = (options?: { enabled?: boolean }) => {
124
124
  });
125
125
  };
126
126
 
127
+ /**
128
+ * Get account storage usage (server-aggregated usage across assets, mail,
129
+ * recordings, etc.). Wraps `oxyServices.getAccountStorageUsage()` in a
130
+ * TanStack query so consumers get caching, background refetch, and a
131
+ * consistent `isLoading` / `refetch` surface instead of hand-rolled
132
+ * `useEffect` fetches.
133
+ */
134
+ export const useAccountStorageUsage = (options?: { enabled?: boolean }) => {
135
+ const { oxyServices, isAuthenticated, activeSessionId } = useOxy();
136
+
137
+ return useQuery<AccountStorageUsageResponse>({
138
+ queryKey: queryKeys.storage.usage(),
139
+ queryFn: async () => {
140
+ return authenticatedApiCall(
141
+ oxyServices,
142
+ activeSessionId,
143
+ () => oxyServices.getAccountStorageUsage()
144
+ );
145
+ },
146
+ enabled: (options?.enabled !== false) && isAuthenticated,
147
+ staleTime: 5 * 60 * 1000,
148
+ gcTime: 30 * 60 * 1000,
149
+ });
150
+ };
151
+
@@ -50,6 +50,7 @@ const PERSISTED_QUERY_PREFIXES: ReadonlyArray<string> = [
50
50
  'sessions',
51
51
  'devices',
52
52
  'privacy',
53
+ 'payments',
53
54
  ];
54
55
 
55
56
  /**
@@ -38,6 +38,36 @@ interface UseWebSSOResult {
38
38
  isFedCMSupported: boolean;
39
39
  }
40
40
 
41
+ /**
42
+ * Module-level guard tracking which (origin + API) signatures have already
43
+ * had a silent SSO attempt this page load.
44
+ *
45
+ * A per-component `useRef` guard resets whenever the provider remounts (route
46
+ * churn, StrictMode double-invoke, error-boundary recovery), which previously
47
+ * allowed silent SSO to re-fire and — combined with a routing redirect loop —
48
+ * produced an accelerating `navigator.credentials.get` retry storm. Keying the
49
+ * guard on a stable signature instead of the component instance makes silent
50
+ * SSO fire EXACTLY ONCE per page load regardless of how many times the
51
+ * provider mounts. The set is intentionally never cleared: a fresh page load
52
+ * (the only thing that can change the answer) starts a fresh module scope.
53
+ */
54
+ const silentSSOAttempted = new Set<string>();
55
+
56
+ /**
57
+ * Build a stable signature for the silent-SSO run-once guard. Two providers
58
+ * pointed at the same API from the same origin share one attempt.
59
+ */
60
+ function ssoSignature(oxyServices: OxyServices): string {
61
+ const origin = typeof window !== 'undefined' ? window.location.origin : 'no-origin';
62
+ let baseURL = '';
63
+ try {
64
+ baseURL = oxyServices.getBaseURL?.() ?? '';
65
+ } catch {
66
+ baseURL = '';
67
+ }
68
+ return `${origin}|${baseURL}`;
69
+ }
70
+
41
71
  /**
42
72
  * Check if we're running in a web browser environment (not React Native)
43
73
  */
@@ -163,7 +193,14 @@ export function useWebSSO({
163
193
  }
164
194
  }, [oxyServices, onSessionFound, onError, fedCMSupported]);
165
195
 
166
- // Auto-check SSO on mount (web only, FedCM only, not on auth domain)
196
+ // Auto-check SSO on mount (web only, FedCM only, not on auth domain).
197
+ //
198
+ // Run-once is enforced by TWO guards:
199
+ // 1. `hasCheckedRef` — cheap per-instance fast-path so effect re-runs
200
+ // (from changing deps) within one mount never re-fire.
201
+ // 2. `silentSSOAttempted` — module-level, survives remounts/StrictMode so
202
+ // silent SSO fires exactly once per page load even if the provider
203
+ // unmounts and remounts.
167
204
  useEffect(() => {
168
205
  if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider(authWebUrl)) {
169
206
  if (isIdentityProvider(authWebUrl)) {
@@ -172,14 +209,23 @@ export function useWebSSO({
172
209
  return;
173
210
  }
174
211
 
212
+ const signature = ssoSignature(oxyServices);
213
+ if (silentSSOAttempted.has(signature)) {
214
+ // Already attempted this page load (e.g. before a remount) — do not
215
+ // re-fire. Mark the local fast-path too so subsequent re-renders skip.
216
+ hasCheckedRef.current = true;
217
+ return;
218
+ }
219
+
175
220
  hasCheckedRef.current = true;
221
+ silentSSOAttempted.add(signature);
176
222
 
177
223
  if (fedCMSupported) {
178
224
  checkSSO();
179
225
  } else {
180
226
  onSSOUnavailable?.();
181
227
  }
182
- }, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
228
+ }, [enabled, checkSSO, fedCMSupported, onSSOUnavailable, oxyServices, authWebUrl]);
183
229
 
184
230
  return {
185
231
  checkSSO,