@oxyhq/core 1.0.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.
Files changed (277) hide show
  1. package/README.md +50 -0
  2. package/dist/cjs/AuthManager.js +361 -0
  3. package/dist/cjs/CrossDomainAuth.js +258 -0
  4. package/dist/cjs/HttpService.js +618 -0
  5. package/dist/cjs/OxyServices.base.js +263 -0
  6. package/dist/cjs/OxyServices.errors.js +22 -0
  7. package/dist/cjs/OxyServices.js +63 -0
  8. package/dist/cjs/constants/version.js +16 -0
  9. package/dist/cjs/crypto/index.js +20 -0
  10. package/dist/cjs/crypto/keyManager.js +887 -0
  11. package/dist/cjs/crypto/polyfill.js +64 -0
  12. package/dist/cjs/crypto/recoveryPhrase.js +169 -0
  13. package/dist/cjs/crypto/signatureService.js +296 -0
  14. package/dist/cjs/i18n/index.js +73 -0
  15. package/dist/cjs/i18n/locales/ar-SA.json +120 -0
  16. package/dist/cjs/i18n/locales/ca-ES.json +120 -0
  17. package/dist/cjs/i18n/locales/de-DE.json +120 -0
  18. package/dist/cjs/i18n/locales/en-US.json +956 -0
  19. package/dist/cjs/i18n/locales/es-ES.json +944 -0
  20. package/dist/cjs/i18n/locales/fr-FR.json +120 -0
  21. package/dist/cjs/i18n/locales/it-IT.json +120 -0
  22. package/dist/cjs/i18n/locales/ja-JP.json +119 -0
  23. package/dist/cjs/i18n/locales/ko-KR.json +120 -0
  24. package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
  25. package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
  26. package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
  27. package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
  28. package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
  29. package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
  30. package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
  31. package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
  32. package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
  33. package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
  34. package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
  35. package/dist/cjs/i18n/locales/pt-PT.json +120 -0
  36. package/dist/cjs/i18n/locales/zh-CN.json +120 -0
  37. package/dist/cjs/index.js +153 -0
  38. package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
  39. package/dist/cjs/mixins/OxyServices.assets.js +380 -0
  40. package/dist/cjs/mixins/OxyServices.auth.js +259 -0
  41. package/dist/cjs/mixins/OxyServices.developer.js +97 -0
  42. package/dist/cjs/mixins/OxyServices.devices.js +116 -0
  43. package/dist/cjs/mixins/OxyServices.features.js +309 -0
  44. package/dist/cjs/mixins/OxyServices.fedcm.js +435 -0
  45. package/dist/cjs/mixins/OxyServices.karma.js +108 -0
  46. package/dist/cjs/mixins/OxyServices.language.js +154 -0
  47. package/dist/cjs/mixins/OxyServices.location.js +43 -0
  48. package/dist/cjs/mixins/OxyServices.payment.js +158 -0
  49. package/dist/cjs/mixins/OxyServices.popup.js +371 -0
  50. package/dist/cjs/mixins/OxyServices.privacy.js +162 -0
  51. package/dist/cjs/mixins/OxyServices.redirect.js +345 -0
  52. package/dist/cjs/mixins/OxyServices.security.js +81 -0
  53. package/dist/cjs/mixins/OxyServices.user.js +355 -0
  54. package/dist/cjs/mixins/OxyServices.utility.js +156 -0
  55. package/dist/cjs/mixins/index.js +79 -0
  56. package/dist/cjs/mixins/mixinHelpers.js +53 -0
  57. package/dist/cjs/models/interfaces.js +20 -0
  58. package/dist/cjs/models/session.js +2 -0
  59. package/dist/cjs/shared/index.js +70 -0
  60. package/dist/cjs/shared/utils/colorUtils.js +153 -0
  61. package/dist/cjs/shared/utils/debugUtils.js +73 -0
  62. package/dist/cjs/shared/utils/errorUtils.js +183 -0
  63. package/dist/cjs/shared/utils/index.js +49 -0
  64. package/dist/cjs/shared/utils/networkUtils.js +183 -0
  65. package/dist/cjs/shared/utils/themeUtils.js +106 -0
  66. package/dist/cjs/utils/apiUtils.js +61 -0
  67. package/dist/cjs/utils/asyncUtils.js +194 -0
  68. package/dist/cjs/utils/cache.js +226 -0
  69. package/dist/cjs/utils/deviceManager.js +205 -0
  70. package/dist/cjs/utils/errorUtils.js +154 -0
  71. package/dist/cjs/utils/index.js +26 -0
  72. package/dist/cjs/utils/languageUtils.js +165 -0
  73. package/dist/cjs/utils/loggerUtils.js +126 -0
  74. package/dist/cjs/utils/platform.js +144 -0
  75. package/dist/cjs/utils/requestUtils.js +209 -0
  76. package/dist/cjs/utils/sessionUtils.js +181 -0
  77. package/dist/cjs/utils/validationUtils.js +173 -0
  78. package/dist/esm/AuthManager.js +356 -0
  79. package/dist/esm/CrossDomainAuth.js +253 -0
  80. package/dist/esm/HttpService.js +614 -0
  81. package/dist/esm/OxyServices.base.js +259 -0
  82. package/dist/esm/OxyServices.errors.js +17 -0
  83. package/dist/esm/OxyServices.js +59 -0
  84. package/dist/esm/constants/version.js +13 -0
  85. package/dist/esm/crypto/index.js +13 -0
  86. package/dist/esm/crypto/keyManager.js +850 -0
  87. package/dist/esm/crypto/polyfill.js +61 -0
  88. package/dist/esm/crypto/recoveryPhrase.js +132 -0
  89. package/dist/esm/crypto/signatureService.js +259 -0
  90. package/dist/esm/i18n/index.js +69 -0
  91. package/dist/esm/i18n/locales/ar-SA.json +120 -0
  92. package/dist/esm/i18n/locales/ca-ES.json +120 -0
  93. package/dist/esm/i18n/locales/de-DE.json +120 -0
  94. package/dist/esm/i18n/locales/en-US.json +956 -0
  95. package/dist/esm/i18n/locales/es-ES.json +944 -0
  96. package/dist/esm/i18n/locales/fr-FR.json +120 -0
  97. package/dist/esm/i18n/locales/it-IT.json +120 -0
  98. package/dist/esm/i18n/locales/ja-JP.json +119 -0
  99. package/dist/esm/i18n/locales/ko-KR.json +120 -0
  100. package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
  101. package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
  102. package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
  103. package/dist/esm/i18n/locales/locales/en-US.json +956 -0
  104. package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
  105. package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
  106. package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
  107. package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
  108. package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
  109. package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
  110. package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
  111. package/dist/esm/i18n/locales/pt-PT.json +120 -0
  112. package/dist/esm/i18n/locales/zh-CN.json +120 -0
  113. package/dist/esm/index.js +55 -0
  114. package/dist/esm/mixins/OxyServices.analytics.js +46 -0
  115. package/dist/esm/mixins/OxyServices.assets.js +377 -0
  116. package/dist/esm/mixins/OxyServices.auth.js +256 -0
  117. package/dist/esm/mixins/OxyServices.developer.js +94 -0
  118. package/dist/esm/mixins/OxyServices.devices.js +113 -0
  119. package/dist/esm/mixins/OxyServices.features.js +306 -0
  120. package/dist/esm/mixins/OxyServices.fedcm.js +433 -0
  121. package/dist/esm/mixins/OxyServices.karma.js +105 -0
  122. package/dist/esm/mixins/OxyServices.language.js +118 -0
  123. package/dist/esm/mixins/OxyServices.location.js +40 -0
  124. package/dist/esm/mixins/OxyServices.payment.js +155 -0
  125. package/dist/esm/mixins/OxyServices.popup.js +369 -0
  126. package/dist/esm/mixins/OxyServices.privacy.js +159 -0
  127. package/dist/esm/mixins/OxyServices.redirect.js +343 -0
  128. package/dist/esm/mixins/OxyServices.security.js +78 -0
  129. package/dist/esm/mixins/OxyServices.user.js +352 -0
  130. package/dist/esm/mixins/OxyServices.utility.js +153 -0
  131. package/dist/esm/mixins/index.js +76 -0
  132. package/dist/esm/mixins/mixinHelpers.js +48 -0
  133. package/dist/esm/models/interfaces.js +17 -0
  134. package/dist/esm/models/session.js +1 -0
  135. package/dist/esm/shared/index.js +31 -0
  136. package/dist/esm/shared/utils/colorUtils.js +143 -0
  137. package/dist/esm/shared/utils/debugUtils.js +65 -0
  138. package/dist/esm/shared/utils/errorUtils.js +170 -0
  139. package/dist/esm/shared/utils/index.js +15 -0
  140. package/dist/esm/shared/utils/networkUtils.js +173 -0
  141. package/dist/esm/shared/utils/themeUtils.js +98 -0
  142. package/dist/esm/utils/apiUtils.js +55 -0
  143. package/dist/esm/utils/asyncUtils.js +179 -0
  144. package/dist/esm/utils/cache.js +218 -0
  145. package/dist/esm/utils/deviceManager.js +168 -0
  146. package/dist/esm/utils/errorUtils.js +146 -0
  147. package/dist/esm/utils/index.js +7 -0
  148. package/dist/esm/utils/languageUtils.js +158 -0
  149. package/dist/esm/utils/loggerUtils.js +115 -0
  150. package/dist/esm/utils/platform.js +102 -0
  151. package/dist/esm/utils/requestUtils.js +203 -0
  152. package/dist/esm/utils/sessionUtils.js +171 -0
  153. package/dist/esm/utils/validationUtils.js +153 -0
  154. package/dist/types/AuthManager.d.ts +143 -0
  155. package/dist/types/CrossDomainAuth.d.ts +160 -0
  156. package/dist/types/HttpService.d.ts +163 -0
  157. package/dist/types/OxyServices.base.d.ts +126 -0
  158. package/dist/types/OxyServices.d.ts +81 -0
  159. package/dist/types/OxyServices.errors.d.ts +11 -0
  160. package/dist/types/constants/version.d.ts +13 -0
  161. package/dist/types/crypto/index.d.ts +11 -0
  162. package/dist/types/crypto/keyManager.d.ts +189 -0
  163. package/dist/types/crypto/polyfill.d.ts +11 -0
  164. package/dist/types/crypto/recoveryPhrase.d.ts +58 -0
  165. package/dist/types/crypto/signatureService.d.ts +86 -0
  166. package/dist/types/i18n/index.d.ts +3 -0
  167. package/dist/types/index.d.ts +50 -0
  168. package/dist/types/mixins/OxyServices.analytics.d.ts +66 -0
  169. package/dist/types/mixins/OxyServices.assets.d.ts +135 -0
  170. package/dist/types/mixins/OxyServices.auth.d.ts +186 -0
  171. package/dist/types/mixins/OxyServices.developer.d.ts +99 -0
  172. package/dist/types/mixins/OxyServices.devices.d.ts +96 -0
  173. package/dist/types/mixins/OxyServices.features.d.ts +228 -0
  174. package/dist/types/mixins/OxyServices.fedcm.d.ts +200 -0
  175. package/dist/types/mixins/OxyServices.karma.d.ts +85 -0
  176. package/dist/types/mixins/OxyServices.language.d.ts +81 -0
  177. package/dist/types/mixins/OxyServices.location.d.ts +64 -0
  178. package/dist/types/mixins/OxyServices.payment.d.ts +111 -0
  179. package/dist/types/mixins/OxyServices.popup.d.ts +205 -0
  180. package/dist/types/mixins/OxyServices.privacy.d.ts +122 -0
  181. package/dist/types/mixins/OxyServices.redirect.d.ts +245 -0
  182. package/dist/types/mixins/OxyServices.security.d.ts +78 -0
  183. package/dist/types/mixins/OxyServices.user.d.ts +182 -0
  184. package/dist/types/mixins/OxyServices.utility.d.ts +93 -0
  185. package/dist/types/mixins/index.d.ts +30 -0
  186. package/dist/types/mixins/mixinHelpers.d.ts +31 -0
  187. package/dist/types/models/interfaces.d.ts +415 -0
  188. package/dist/types/models/session.d.ts +27 -0
  189. package/dist/types/shared/index.d.ts +28 -0
  190. package/dist/types/shared/utils/colorUtils.d.ts +104 -0
  191. package/dist/types/shared/utils/debugUtils.d.ts +48 -0
  192. package/dist/types/shared/utils/errorUtils.d.ts +97 -0
  193. package/dist/types/shared/utils/index.d.ts +13 -0
  194. package/dist/types/shared/utils/networkUtils.d.ts +139 -0
  195. package/dist/types/shared/utils/themeUtils.d.ts +90 -0
  196. package/dist/types/utils/apiUtils.d.ts +53 -0
  197. package/dist/types/utils/asyncUtils.d.ts +58 -0
  198. package/dist/types/utils/cache.d.ts +127 -0
  199. package/dist/types/utils/deviceManager.d.ts +65 -0
  200. package/dist/types/utils/errorUtils.d.ts +46 -0
  201. package/dist/types/utils/index.d.ts +6 -0
  202. package/dist/types/utils/languageUtils.d.ts +37 -0
  203. package/dist/types/utils/loggerUtils.d.ts +48 -0
  204. package/dist/types/utils/platform.d.ts +40 -0
  205. package/dist/types/utils/requestUtils.d.ts +123 -0
  206. package/dist/types/utils/sessionUtils.d.ts +54 -0
  207. package/dist/types/utils/validationUtils.d.ts +85 -0
  208. package/package.json +84 -0
  209. package/src/AuthManager.ts +436 -0
  210. package/src/CrossDomainAuth.ts +307 -0
  211. package/src/HttpService.ts +752 -0
  212. package/src/OxyServices.base.ts +334 -0
  213. package/src/OxyServices.errors.ts +26 -0
  214. package/src/OxyServices.ts +129 -0
  215. package/src/constants/version.ts +15 -0
  216. package/src/crypto/index.ts +25 -0
  217. package/src/crypto/keyManager.ts +962 -0
  218. package/src/crypto/polyfill.ts +70 -0
  219. package/src/crypto/recoveryPhrase.ts +166 -0
  220. package/src/crypto/signatureService.ts +323 -0
  221. package/src/i18n/index.ts +75 -0
  222. package/src/i18n/locales/ar-SA.json +120 -0
  223. package/src/i18n/locales/ca-ES.json +120 -0
  224. package/src/i18n/locales/de-DE.json +120 -0
  225. package/src/i18n/locales/en-US.json +956 -0
  226. package/src/i18n/locales/es-ES.json +944 -0
  227. package/src/i18n/locales/fr-FR.json +120 -0
  228. package/src/i18n/locales/it-IT.json +120 -0
  229. package/src/i18n/locales/ja-JP.json +119 -0
  230. package/src/i18n/locales/ko-KR.json +120 -0
  231. package/src/i18n/locales/pt-PT.json +120 -0
  232. package/src/i18n/locales/zh-CN.json +120 -0
  233. package/src/index.ts +153 -0
  234. package/src/mixins/OxyServices.analytics.ts +53 -0
  235. package/src/mixins/OxyServices.assets.ts +412 -0
  236. package/src/mixins/OxyServices.auth.ts +358 -0
  237. package/src/mixins/OxyServices.developer.ts +114 -0
  238. package/src/mixins/OxyServices.devices.ts +119 -0
  239. package/src/mixins/OxyServices.features.ts +428 -0
  240. package/src/mixins/OxyServices.fedcm.ts +494 -0
  241. package/src/mixins/OxyServices.karma.ts +111 -0
  242. package/src/mixins/OxyServices.language.ts +127 -0
  243. package/src/mixins/OxyServices.location.ts +46 -0
  244. package/src/mixins/OxyServices.payment.ts +163 -0
  245. package/src/mixins/OxyServices.popup.ts +443 -0
  246. package/src/mixins/OxyServices.privacy.ts +182 -0
  247. package/src/mixins/OxyServices.redirect.ts +397 -0
  248. package/src/mixins/OxyServices.security.ts +103 -0
  249. package/src/mixins/OxyServices.user.ts +392 -0
  250. package/src/mixins/OxyServices.utility.ts +191 -0
  251. package/src/mixins/index.ts +91 -0
  252. package/src/mixins/mixinHelpers.ts +69 -0
  253. package/src/models/interfaces.ts +511 -0
  254. package/src/models/session.ts +30 -0
  255. package/src/shared/index.ts +82 -0
  256. package/src/shared/utils/colorUtils.ts +155 -0
  257. package/src/shared/utils/debugUtils.ts +73 -0
  258. package/src/shared/utils/errorUtils.ts +181 -0
  259. package/src/shared/utils/index.ts +59 -0
  260. package/src/shared/utils/networkUtils.ts +248 -0
  261. package/src/shared/utils/themeUtils.ts +115 -0
  262. package/src/types/bip39.d.ts +32 -0
  263. package/src/types/buffer.d.ts +97 -0
  264. package/src/types/color.d.ts +20 -0
  265. package/src/types/elliptic.d.ts +62 -0
  266. package/src/utils/apiUtils.ts +88 -0
  267. package/src/utils/asyncUtils.ts +252 -0
  268. package/src/utils/cache.ts +264 -0
  269. package/src/utils/deviceManager.ts +198 -0
  270. package/src/utils/errorUtils.ts +216 -0
  271. package/src/utils/index.ts +21 -0
  272. package/src/utils/languageUtils.ts +174 -0
  273. package/src/utils/loggerUtils.ts +153 -0
  274. package/src/utils/platform.ts +117 -0
  275. package/src/utils/requestUtils.ts +237 -0
  276. package/src/utils/sessionUtils.ts +206 -0
  277. package/src/utils/validationUtils.ts +174 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * User Management Methods Mixin
3
+ */
4
+ import type { User, Notification, SearchProfilesResponse, PaginationInfo } from '../models/interfaces';
5
+ import type { OxyServicesBase } from '../OxyServices.base';
6
+ import { buildSearchParams, buildPaginationParams, type PaginationParams } from '../utils/apiUtils';
7
+
8
+ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T) {
9
+ return class extends Base {
10
+ constructor(...args: any[]) {
11
+ super(...(args as [any]));
12
+ }
13
+ /**
14
+ * Get profile by username
15
+ */
16
+ async getProfileByUsername(username: string): Promise<User> {
17
+ try {
18
+ return await this.makeRequest<User>('GET', `/api/profiles/username/${username}`, undefined, {
19
+ cache: true,
20
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
21
+ });
22
+ } catch (error) {
23
+ throw this.handleError(error);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Search user profiles
29
+ */
30
+ async searchProfiles(query: string, pagination?: PaginationParams): Promise<SearchProfilesResponse> {
31
+ try {
32
+ const params = { query, ...pagination };
33
+ const searchParams = buildSearchParams(params);
34
+ const paramsObj = Object.fromEntries(searchParams.entries());
35
+
36
+ const response = await this.makeRequest<SearchProfilesResponse | User[]>(
37
+ 'GET',
38
+ '/api/profiles/search',
39
+ paramsObj,
40
+ {
41
+ cache: true,
42
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
43
+ }
44
+ );
45
+
46
+ // New API shape: { data: User[], pagination: {...} }
47
+ const isSearchProfilesResponse = (payload: unknown): payload is SearchProfilesResponse =>
48
+ typeof payload === 'object' &&
49
+ payload !== null &&
50
+ Array.isArray((payload as SearchProfilesResponse).data);
51
+
52
+ if (isSearchProfilesResponse(response)) {
53
+ const typedResponse = response;
54
+ const paginationInfo: PaginationInfo = typedResponse.pagination ?? {
55
+ total: typedResponse.data.length,
56
+ limit: pagination?.limit ?? typedResponse.data.length,
57
+ offset: pagination?.offset ?? 0,
58
+ hasMore: typedResponse.data.length === (pagination?.limit ?? typedResponse.data.length) &&
59
+ (pagination?.limit ?? typedResponse.data.length) > 0,
60
+ };
61
+
62
+ return {
63
+ data: typedResponse.data,
64
+ pagination: paginationInfo,
65
+ };
66
+ }
67
+
68
+ // Legacy API shape: returns raw User[]
69
+ if (Array.isArray(response)) {
70
+ const fallbackLimit = pagination?.limit ?? response.length;
71
+ const fallbackPagination: PaginationInfo = {
72
+ total: response.length,
73
+ limit: fallbackLimit,
74
+ offset: pagination?.offset ?? 0,
75
+ hasMore: fallbackLimit > 0 && response.length === fallbackLimit,
76
+ };
77
+
78
+ return { data: response, pagination: fallbackPagination };
79
+ }
80
+
81
+ // If response is unexpected, throw an error
82
+ throw new Error('Unexpected search response format');
83
+ } catch (error) {
84
+ throw this.handleError(error);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get profile recommendations
90
+ */
91
+ async getProfileRecommendations(): Promise<Array<{
92
+ id: string;
93
+ username: string;
94
+ name?: { first?: string; last?: string; full?: string };
95
+ description?: string;
96
+ _count?: { followers: number; following: number };
97
+ [key: string]: any;
98
+ }>> {
99
+ return this.withAuthRetry(async () => {
100
+ return await this.makeRequest('GET', '/api/profiles/recommendations', undefined, { cache: true });
101
+ }, 'getProfileRecommendations');
102
+ }
103
+
104
+ /**
105
+ * Get user by ID
106
+ */
107
+ async getUserById(userId: string): Promise<User> {
108
+ try {
109
+ return await this.makeRequest<User>('GET', `/api/users/${userId}`, undefined, {
110
+ cache: true,
111
+ cacheTTL: 5 * 60 * 1000, // 5 minutes cache
112
+ });
113
+ } catch (error) {
114
+ throw this.handleError(error);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Get current user
120
+ */
121
+ async getCurrentUser(): Promise<User> {
122
+ return this.withAuthRetry(async () => {
123
+ return await this.makeRequest<User>('GET', '/api/users/me', undefined, {
124
+ cache: true,
125
+ cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
126
+ });
127
+ }, 'getCurrentUser');
128
+ }
129
+
130
+ /**
131
+ * Update user profile
132
+ * TanStack Query handles offline queuing automatically
133
+ */
134
+ async updateProfile(updates: Record<string, any>): Promise<User> {
135
+ try {
136
+ return await this.makeRequest<User>('PUT', '/api/users/me', updates, { cache: false });
137
+ } catch (error) {
138
+ const errorAny = error as any;
139
+ const errorMessage = error instanceof Error ? error.message : String(error);
140
+ const status = errorAny?.status || errorAny?.response?.status;
141
+
142
+ // Check if it's an authentication error (401)
143
+ const isAuthError = status === 401 ||
144
+ errorMessage.includes('Authentication required') ||
145
+ errorMessage.includes('Invalid or missing authorization header');
146
+
147
+ // If authentication error and we don't have a token, this might be an offline session
148
+ // The caller should handle syncing the session before retrying
149
+ if (isAuthError && !this.hasValidToken()) {
150
+ // Re-throw with a specific message so the caller knows to sync
151
+ throw new Error('AUTH_REQUIRED_OFFLINE_SESSION: Session needs to be synced to get a token');
152
+ }
153
+
154
+ throw this.handleError(error);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Get privacy settings for a user
160
+ * @param userId - The user ID (defaults to current user)
161
+ */
162
+ async getPrivacySettings(userId?: string): Promise<any> {
163
+ try {
164
+ const id = userId || (await this.getCurrentUser()).id;
165
+ return await this.makeRequest<any>('GET', `/api/privacy/${id}/privacy`, undefined, {
166
+ cache: true,
167
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
168
+ });
169
+ } catch (error) {
170
+ throw this.handleError(error);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Update privacy settings
176
+ * @param settings - Partial privacy settings object
177
+ * @param userId - The user ID (defaults to current user)
178
+ */
179
+ async updatePrivacySettings(settings: Record<string, any>, userId?: string): Promise<any> {
180
+ try {
181
+ const id = userId || (await this.getCurrentUser()).id;
182
+ return await this.makeRequest<any>('PATCH', `/api/privacy/${id}/privacy`, settings, {
183
+ cache: false,
184
+ });
185
+ } catch (error) {
186
+ throw this.handleError(error);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Request account verification
192
+ */
193
+ async requestAccountVerification(reason: string, evidence?: string): Promise<{ message: string; requestId: string }> {
194
+ try {
195
+ return await this.makeRequest<{ message: string; requestId: string }>('POST', '/api/users/verify/request', {
196
+ reason,
197
+ evidence,
198
+ }, { cache: false });
199
+ } catch (error) {
200
+ throw this.handleError(error);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Download account data export
206
+ */
207
+ async downloadAccountData(format: 'json' | 'csv' = 'json'): Promise<Blob> {
208
+ try {
209
+ // Use httpService for blob responses (it handles blob responses automatically)
210
+ const result = await this.getClient().request<Blob>({
211
+ method: 'GET',
212
+ url: `/api/users/me/data`,
213
+ params: { format },
214
+ cache: false,
215
+ });
216
+
217
+ return result;
218
+ } catch (error) {
219
+ throw this.handleError(error);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Delete account permanently
225
+ * @param password - User password for confirmation
226
+ * @param confirmText - Confirmation text (usually username)
227
+ */
228
+ async deleteAccount(password: string, confirmText: string): Promise<{ message: string }> {
229
+ try {
230
+ return await this.makeRequest<{ message: string }>('DELETE', '/api/users/me', {
231
+ password,
232
+ confirmText,
233
+ }, { cache: false });
234
+ } catch (error) {
235
+ throw this.handleError(error);
236
+ }
237
+ }
238
+
239
+
240
+ /**
241
+ * Follow a user
242
+ */
243
+ async followUser(userId: string): Promise<{ success: boolean; message: string }> {
244
+ try {
245
+ return await this.makeRequest('POST', `/api/users/${userId}/follow`, undefined, { cache: false });
246
+ } catch (error) {
247
+ throw this.handleError(error);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Unfollow a user
253
+ */
254
+ async unfollowUser(userId: string): Promise<{ success: boolean; message: string }> {
255
+ try {
256
+ return await this.makeRequest('DELETE', `/api/users/${userId}/follow`, undefined, { cache: false });
257
+ } catch (error) {
258
+ throw this.handleError(error);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Get follow status
264
+ */
265
+ async getFollowStatus(userId: string): Promise<{ isFollowing: boolean }> {
266
+ try {
267
+ return await this.makeRequest('GET', `/api/users/${userId}/follow-status`, undefined, {
268
+ cache: true,
269
+ cacheTTL: 1 * 60 * 1000, // 1 minute cache
270
+ });
271
+ } catch (error) {
272
+ throw this.handleError(error);
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Get user followers
278
+ */
279
+ async getUserFollowers(
280
+ userId: string,
281
+ pagination?: PaginationParams
282
+ ): Promise<{ followers: User[]; total: number; hasMore: boolean }> {
283
+ try {
284
+ const params = buildPaginationParams(pagination || {});
285
+ const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/followers`, params, {
286
+ cache: true,
287
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
288
+ });
289
+ return {
290
+ followers: response.data || [],
291
+ total: response.pagination.total,
292
+ hasMore: response.pagination.hasMore,
293
+ };
294
+ } catch (error) {
295
+ throw this.handleError(error);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Get user following
301
+ */
302
+ async getUserFollowing(
303
+ userId: string,
304
+ pagination?: PaginationParams
305
+ ): Promise<{ following: User[]; total: number; hasMore: boolean }> {
306
+ try {
307
+ const params = buildPaginationParams(pagination || {});
308
+ const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/following`, params, {
309
+ cache: true,
310
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
311
+ });
312
+ return {
313
+ following: response.data || [],
314
+ total: response.pagination.total,
315
+ hasMore: response.pagination.hasMore,
316
+ };
317
+ } catch (error) {
318
+ throw this.handleError(error);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Get notifications
324
+ */
325
+ async getNotifications(): Promise<Notification[]> {
326
+ return this.withAuthRetry(async () => {
327
+ return await this.makeRequest<Notification[]>('GET', '/api/notifications', undefined, {
328
+ cache: false, // Don't cache notifications - always get fresh data
329
+ });
330
+ }, 'getNotifications');
331
+ }
332
+
333
+ /**
334
+ * Get unread notification count
335
+ */
336
+ async getUnreadCount(): Promise<number> {
337
+ try {
338
+ const res = await this.makeRequest<{ count: number }>('GET', '/api/notifications/unread-count', undefined, {
339
+ cache: false, // Don't cache unread count - always get fresh data
340
+ });
341
+ return res.count;
342
+ } catch (error) {
343
+ throw this.handleError(error);
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Create notification
349
+ */
350
+ async createNotification(data: Partial<Notification>): Promise<Notification> {
351
+ try {
352
+ return await this.makeRequest<Notification>('POST', '/api/notifications', data, { cache: false });
353
+ } catch (error) {
354
+ throw this.handleError(error);
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Mark notification as read
360
+ */
361
+ async markNotificationAsRead(notificationId: string): Promise<void> {
362
+ try {
363
+ await this.makeRequest('PUT', `/api/notifications/${notificationId}/read`, undefined, { cache: false });
364
+ } catch (error) {
365
+ throw this.handleError(error);
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Mark all notifications as read
371
+ */
372
+ async markAllNotificationsAsRead(): Promise<void> {
373
+ try {
374
+ await this.makeRequest('PUT', '/api/notifications/read-all', undefined, { cache: false });
375
+ } catch (error) {
376
+ throw this.handleError(error);
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Delete notification
382
+ */
383
+ async deleteNotification(notificationId: string): Promise<void> {
384
+ try {
385
+ await this.makeRequest('DELETE', `/api/notifications/${notificationId}`, undefined, { cache: false });
386
+ } catch (error) {
387
+ throw this.handleError(error);
388
+ }
389
+ }
390
+ };
391
+ }
392
+
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Utility Methods Mixin
3
+ *
4
+ * Provides utility methods including link metadata fetching
5
+ * and Express.js authentication middleware
6
+ */
7
+ import { jwtDecode } from 'jwt-decode';
8
+ import type { ApiError, User } from '../models/interfaces';
9
+ import type { OxyServicesBase } from '../OxyServices.base';
10
+ import { CACHE_TIMES } from './mixinHelpers';
11
+
12
+ interface JwtPayload {
13
+ exp?: number;
14
+ userId?: string;
15
+ id?: string;
16
+ sessionId?: string;
17
+ [key: string]: any;
18
+ }
19
+
20
+ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T) {
21
+ return class extends Base {
22
+ constructor(...args: any[]) {
23
+ super(...(args as [any]));
24
+ }
25
+ /**
26
+ * Fetch link metadata
27
+ */
28
+ async fetchLinkMetadata(url: string): Promise<{
29
+ url: string;
30
+ title: string;
31
+ description: string;
32
+ image?: string;
33
+ }> {
34
+ try {
35
+ return await this.makeRequest<{
36
+ url: string;
37
+ title: string;
38
+ description: string;
39
+ image?: string;
40
+ }>('GET', '/api/link-metadata', { url }, {
41
+ cache: true,
42
+ cacheTTL: CACHE_TIMES.EXTRA_LONG,
43
+ });
44
+ } catch (error) {
45
+ throw this.handleError(error);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Simple Express.js authentication middleware
51
+ *
52
+ * Built-in authentication middleware that validates JWT tokens and adds user data to requests.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Basic usage - just add it to your routes
57
+ * app.use('/api/protected', oxyServices.auth());
58
+ *
59
+ * // With debug logging
60
+ * app.use('/api/protected', oxyServices.auth({ debug: true }));
61
+ *
62
+ * // With custom error handling
63
+ * app.use('/api/protected', oxyServices.auth({
64
+ * onError: (error) => console.error('Auth failed:', error)
65
+ * }));
66
+ *
67
+ * // Load full user data
68
+ * app.use('/api/protected', oxyServices.auth({ loadUser: true }));
69
+ * ```
70
+ *
71
+ * @param options Optional configuration
72
+ * @param options.debug Enable debug logging (default: false)
73
+ * @param options.onError Custom error handler
74
+ * @param options.loadUser Load full user data (default: false for performance)
75
+ * @param options.session Use session-based validation (default: false)
76
+ * @returns Express middleware function
77
+ */
78
+ auth(options: {
79
+ debug?: boolean;
80
+ onError?: (error: ApiError) => any;
81
+ loadUser?: boolean;
82
+ session?: boolean;
83
+ } = {}) {
84
+ const { debug = false, onError, loadUser = false, session = false } = options;
85
+
86
+ // Return a synchronous middleware function
87
+ return (req: any, res: any, next: any) => {
88
+ try {
89
+ // Extract token from Authorization header
90
+ const authHeader = req.headers['authorization'];
91
+ const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
92
+
93
+ if (debug) {
94
+ console.log(`🔐 Auth: Processing ${req.method} ${req.path}`);
95
+ console.log(`🔐 Auth: Token present: ${!!token}`);
96
+ }
97
+
98
+ if (!token) {
99
+ const error = {
100
+ message: 'Access token required',
101
+ code: 'MISSING_TOKEN',
102
+ status: 401
103
+ };
104
+
105
+ if (debug) console.log(`❌ Auth: Missing token`);
106
+
107
+ if (onError) return onError(error);
108
+ return res.status(401).json(error);
109
+ }
110
+
111
+ // Decode and validate token
112
+ let decoded: JwtPayload;
113
+ try {
114
+ decoded = jwtDecode<JwtPayload>(token);
115
+
116
+ if (debug) {
117
+ console.log(`🔐 Auth: Token decoded, User ID: ${decoded.userId || decoded.id}`);
118
+ }
119
+ } catch (decodeError) {
120
+ const error = {
121
+ message: 'Invalid token format',
122
+ code: 'INVALID_TOKEN_FORMAT',
123
+ status: 403
124
+ };
125
+
126
+ if (debug) console.log(`❌ Auth: Token decode failed`);
127
+
128
+ if (onError) return onError(error);
129
+ return res.status(403).json(error);
130
+ }
131
+
132
+ const userId = decoded.userId || decoded.id;
133
+ if (!userId) {
134
+ const error = {
135
+ message: 'Token missing user ID',
136
+ code: 'INVALID_TOKEN_PAYLOAD',
137
+ status: 403
138
+ };
139
+
140
+ if (debug) console.log(`❌ Auth: Token missing user ID`);
141
+
142
+ if (onError) return onError(error);
143
+ return res.status(403).json(error);
144
+ }
145
+
146
+ // Check token expiration
147
+ if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
148
+ const error = {
149
+ message: 'Token expired',
150
+ code: 'TOKEN_EXPIRED',
151
+ status: 403
152
+ };
153
+
154
+ if (debug) console.log(`❌ Auth: Token expired`);
155
+
156
+ if (onError) return onError(error);
157
+ return res.status(403).json(error);
158
+ }
159
+
160
+ // For now, skip session validation to keep it simple
161
+ // Session validation can be added later if needed
162
+
163
+ // Set request properties immediately
164
+ req.userId = userId;
165
+ req.accessToken = token;
166
+ req.user = { id: userId } as User;
167
+
168
+ // Automatically authenticate the OxyServices instance so all subsequent API calls are authenticated
169
+ this.setTokens(token);
170
+
171
+ if (debug) {
172
+ console.log(`✅ Auth: Authentication successful for user ${userId}`);
173
+ }
174
+
175
+ next();
176
+ } catch (error) {
177
+ const apiError = this.handleError(error) as any;
178
+
179
+ if (debug) {
180
+ // Debug logging - using console.log is acceptable here for development
181
+ console.log(`❌ Auth: Unexpected error:`, apiError);
182
+ }
183
+
184
+ if (onError) return onError(apiError);
185
+ return res.status((apiError && apiError.status) || 500).json(apiError);
186
+ }
187
+ };
188
+ }
189
+ };
190
+ }
191
+
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Centralized mixin exports and composition helper
3
+ *
4
+ * This module provides a clean way to compose all mixins
5
+ * and ensures consistent ordering for better maintainability
6
+ */
7
+
8
+ import { OxyServicesBase } from '../OxyServices.base';
9
+ import { OxyServicesAuthMixin } from './OxyServices.auth';
10
+ import { OxyServicesFedCMMixin } from './OxyServices.fedcm';
11
+ import { OxyServicesPopupAuthMixin } from './OxyServices.popup';
12
+ import { OxyServicesRedirectAuthMixin } from './OxyServices.redirect';
13
+ import { OxyServicesUserMixin } from './OxyServices.user';
14
+ import { OxyServicesPrivacyMixin } from './OxyServices.privacy';
15
+ import { OxyServicesLanguageMixin } from './OxyServices.language';
16
+ import { OxyServicesPaymentMixin } from './OxyServices.payment';
17
+ import { OxyServicesKarmaMixin } from './OxyServices.karma';
18
+ import { OxyServicesAssetsMixin } from './OxyServices.assets';
19
+ import { OxyServicesDeveloperMixin } from './OxyServices.developer';
20
+ import { OxyServicesLocationMixin } from './OxyServices.location';
21
+ import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
22
+ import { OxyServicesDevicesMixin } from './OxyServices.devices';
23
+ import { OxyServicesSecurityMixin } from './OxyServices.security';
24
+ import { OxyServicesUtilityMixin } from './OxyServices.utility';
25
+ import { OxyServicesFeaturesMixin } from './OxyServices.features';
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ type MixinFunction = (Base: any) => any;
29
+
30
+ /**
31
+ * Mixin pipeline - applied in order from first to last.
32
+ *
33
+ * Order matters for dependencies:
34
+ * 1. Base auth mixin first (required by all others)
35
+ * 2. Cross-domain auth mixins (FedCM, Popup, Redirect)
36
+ * 3. User mixin (requires auth)
37
+ * 4. Feature mixins (can depend on user)
38
+ * 5. Utility mixin last (augments all)
39
+ *
40
+ * To add a new mixin: insert it at the appropriate position in this array.
41
+ */
42
+ const MIXIN_PIPELINE: MixinFunction[] = [
43
+ // Base authentication
44
+ OxyServicesAuthMixin,
45
+
46
+ // Cross-domain authentication (web-only)
47
+ // - FedCM: Modern browser-native identity federation (Google-style)
48
+ // - Popup: OAuth2-style popup authentication
49
+ // - Redirect: Traditional redirect-based authentication
50
+ OxyServicesFedCMMixin,
51
+ OxyServicesPopupAuthMixin,
52
+ OxyServicesRedirectAuthMixin,
53
+
54
+ // User management (requires auth)
55
+ OxyServicesUserMixin,
56
+ OxyServicesPrivacyMixin,
57
+
58
+ // Feature mixins
59
+ OxyServicesLanguageMixin,
60
+ OxyServicesPaymentMixin,
61
+ OxyServicesKarmaMixin,
62
+ OxyServicesAssetsMixin,
63
+ OxyServicesDeveloperMixin,
64
+ OxyServicesLocationMixin,
65
+ OxyServicesAnalyticsMixin,
66
+ OxyServicesDevicesMixin,
67
+ OxyServicesSecurityMixin,
68
+ OxyServicesFeaturesMixin,
69
+
70
+ // Utility (last, can use all above)
71
+ OxyServicesUtilityMixin,
72
+ ];
73
+
74
+ /**
75
+ * Composes all OxyServices mixins using a pipeline pattern.
76
+ *
77
+ * This is equivalent to the nested calls but more readable and maintainable.
78
+ * Adding a new mixin: just add it to MIXIN_PIPELINE at the appropriate position.
79
+ *
80
+ * @returns The fully composed OxyServices class with all mixins applied
81
+ */
82
+ export function composeOxyServices() {
83
+ return MIXIN_PIPELINE.reduce(
84
+ (Base, mixin) => mixin(Base),
85
+ OxyServicesBase as unknown as ReturnType<MixinFunction>
86
+ );
87
+ }
88
+
89
+ // Export the pipeline for testing/debugging
90
+ export { MIXIN_PIPELINE };
91
+