@oxyhq/core 1.8.0 → 1.9.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 (35) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -0
  2. package/dist/cjs/HttpService.js +8 -55
  3. package/dist/cjs/crypto/signatureService.js +1 -1
  4. package/dist/cjs/index.js +8 -1
  5. package/dist/cjs/mixins/OxyServices.assets.js +13 -15
  6. package/dist/cjs/utils/accountUtils.js +49 -0
  7. package/dist/cjs/utils/avatarUtils.js +28 -0
  8. package/dist/esm/.tsbuildinfo +1 -0
  9. package/dist/esm/HttpService.js +8 -55
  10. package/dist/esm/crypto/signatureService.js +1 -1
  11. package/dist/esm/index.js +4 -0
  12. package/dist/esm/mixins/OxyServices.assets.js +13 -15
  13. package/dist/esm/utils/accountUtils.js +44 -0
  14. package/dist/esm/utils/avatarUtils.js +25 -0
  15. package/dist/types/.tsbuildinfo +1 -0
  16. package/dist/types/HttpService.d.ts +5 -57
  17. package/dist/types/OxyServices.d.ts +1 -0
  18. package/dist/types/index.d.ts +3 -0
  19. package/dist/types/mixins/OxyServices.assets.d.ts +12 -3
  20. package/dist/types/mixins/OxyServices.user.d.ts +8 -1
  21. package/dist/types/models/interfaces.d.ts +9 -0
  22. package/dist/types/utils/accountUtils.d.ts +36 -0
  23. package/dist/types/utils/avatarUtils.d.ts +16 -0
  24. package/package.json +2 -2
  25. package/src/HttpService.ts +13 -60
  26. package/src/OxyServices.ts +3 -0
  27. package/src/crypto/signatureService.ts +2 -2
  28. package/src/index.ts +7 -0
  29. package/src/mixins/OxyServices.assets.ts +16 -17
  30. package/src/mixins/OxyServices.user.ts +4 -1
  31. package/src/models/interfaces.ts +11 -0
  32. package/src/types/expo-crypto.d.ts +16 -0
  33. package/src/types/expo-secure-store.d.ts +17 -0
  34. package/src/utils/accountUtils.ts +69 -0
  35. package/src/utils/avatarUtils.ts +37 -0
@@ -141,6 +141,9 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
141
141
  authSocket(options?: {
142
142
  debug?: boolean;
143
143
  }): (socket: any, next: (err?: Error) => void) => Promise<void>;
144
+
145
+ // Asset management
146
+ assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<unknown>;
144
147
  }
145
148
 
146
149
  // Re-export error classes for convenience
@@ -80,8 +80,8 @@ export class SignatureService {
80
80
  if (isReactNative()) {
81
81
  const Crypto = await initExpoCrypto();
82
82
  const randomBytes = await Crypto.getRandomBytesAsync(32);
83
- return Array.from(randomBytes)
84
- .map((b: number) => b.toString(16).padStart(2, '0'))
83
+ return Array.from(new Uint8Array(randomBytes))
84
+ .map((b) => b.toString(16).padStart(2, '0'))
85
85
  .join('');
86
86
  }
87
87
 
package/src/index.ts CHANGED
@@ -163,6 +163,13 @@ export {
163
163
  } from './utils/loggerUtils';
164
164
  export type { LogContext } from './utils/loggerUtils';
165
165
 
166
+ // --- Avatar Utilities ---
167
+ export { updateAvatarVisibility } from './utils/avatarUtils';
168
+
169
+ // --- Account Utilities ---
170
+ export { buildAccountsArray, createQuickAccount } from './utils/accountUtils';
171
+ export type { QuickAccount } from './utils/accountUtils';
172
+
166
173
  // Default export
167
174
  import { OxyServices } from './OxyServices';
168
175
  export default OxyServices;
@@ -160,28 +160,27 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
160
160
  }
161
161
 
162
162
  /**
163
- * Upload file using Central Asset Service
163
+ * Upload file using Central Asset Service.
164
+ *
165
+ * Accepts either a web File/Blob or a React Native file descriptor
166
+ * ({uri, type, name, size}). RN descriptors are passed directly to
167
+ * FormData.append, which handles them natively.
164
168
  */
165
- async assetUpload(file: File, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>, onProgress?: (progress: number) => void): Promise<any> {
166
- const fileName = file.name || 'unknown';
167
- const fileSize = file.size;
168
-
169
+ async assetUpload(file: File | { uri: string; type?: string; name?: string; size?: number }, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>, onProgress?: (progress: number) => void): Promise<any> {
170
+ const fileName = 'name' in file && file.name ? file.name : 'unknown';
171
+ const fileSize = 'size' in file && file.size ? file.size : 0;
172
+
169
173
  try {
170
174
  const formData = new FormData();
171
- // Convert File to Blob to avoid read-only 'name' property error in Expo 54
172
- // This is a known issue in Expo SDK 52+ where FormData tries to set the read-only 'name' property
173
- let fileBlob: Blob;
174
- if (file instanceof Blob) {
175
- // Already a Blob, use directly
176
- fileBlob = file;
177
- } else if (typeof (file as any).blob === 'function') {
178
- // Use async blob() method if available (Expo 54+ recommended approach)
179
- fileBlob = await (file as any).blob();
175
+
176
+ if ('uri' in file && typeof file.uri === 'string') {
177
+ // React Native file descriptor — RN's FormData handles {uri, type, name} natively
178
+ formData.append('file', file as unknown as Blob, fileName);
179
+ } else if (file instanceof Blob) {
180
+ formData.append('file', file, fileName);
180
181
  } else {
181
- // Fallback: create Blob from File (works in all environments)
182
- fileBlob = new Blob([file], { type: (file as any).type || 'application/octet-stream' });
182
+ formData.append('file', new Blob([file as unknown as BlobPart], { type: 'application/octet-stream' }), fileName);
183
183
  }
184
- formData.append('file', fileBlob, fileName);
185
184
  if (visibility) {
186
185
  formData.append('visibility', visibility);
187
186
  }
@@ -93,8 +93,11 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
93
93
  username: string;
94
94
  name?: { first?: string; last?: string; full?: string };
95
95
  description?: string;
96
+ isFederated?: boolean;
97
+ instance?: string;
98
+ federation?: { actorUri?: string; domain?: string; actorId?: string };
96
99
  _count?: { followers: number; following: number };
97
- [key: string]: any;
100
+ [key: string]: unknown;
98
101
  }>> {
99
102
  return this.withAuthRetry(async () => {
100
103
  return await this.makeRequest('GET', '/profiles/recommendations', undefined, { cache: true });
@@ -30,6 +30,8 @@ export interface User {
30
30
  email?: string;
31
31
  // Avatar file id (asset id)
32
32
  avatar?: string;
33
+ // Named color preset (e.g. 'teal', 'blue', 'purple')
34
+ color?: string;
33
35
  // Privacy and security settings
34
36
  privacySettings?: {
35
37
  [key: string]: unknown;
@@ -58,6 +60,15 @@ export interface User {
58
60
  following?: number;
59
61
  };
60
62
  accountExpiresAfterInactivityDays?: number | null; // Days of inactivity before account expires (null = never expire)
63
+ // Fediverse / ActivityPub support
64
+ type?: 'local' | 'federated';
65
+ isFederated?: boolean;
66
+ instance?: string;
67
+ federation?: {
68
+ actorUri?: string;
69
+ domain?: string;
70
+ actorId?: string;
71
+ };
61
72
  [key: string]: unknown;
62
73
  }
63
74
 
@@ -0,0 +1,16 @@
1
+ declare module 'expo-crypto' {
2
+ export enum CryptoDigestAlgorithm {
3
+ SHA256 = 'SHA-256',
4
+ SHA384 = 'SHA-384',
5
+ SHA512 = 'SHA-512',
6
+ }
7
+
8
+ export function digestStringAsync(
9
+ algorithm: CryptoDigestAlgorithm,
10
+ data: string,
11
+ ): Promise<string>;
12
+
13
+ export function getRandomBytes(byteCount: number): Uint8Array;
14
+
15
+ export function getRandomBytesAsync(byteCount: number): Promise<Uint8Array>;
16
+ }
@@ -0,0 +1,17 @@
1
+ declare module 'expo-secure-store' {
2
+ export interface SecureStoreOptions {
3
+ keychainAccessible?: number;
4
+ keychainAccessGroup?: string;
5
+ keychainService?: string;
6
+ requireAuthentication?: boolean;
7
+ }
8
+
9
+ export const WHEN_UNLOCKED: number;
10
+ export const AFTER_FIRST_UNLOCK: number;
11
+ export const WHEN_UNLOCKED_THIS_DEVICE_ONLY: number;
12
+ export const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: number;
13
+
14
+ export function getItemAsync(key: string, options?: SecureStoreOptions): Promise<string | null>;
15
+ export function setItemAsync(key: string, value: string, options?: SecureStoreOptions): Promise<void>;
16
+ export function deleteItemAsync(key: string, options?: SecureStoreOptions): Promise<void>;
17
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Shared account types and pure helper functions.
3
+ * Used by both @oxyhq/services (React Native) and @oxyhq/auth (Web) account stores.
4
+ */
5
+
6
+ export interface QuickAccount {
7
+ sessionId: string;
8
+ userId?: string;
9
+ username: string;
10
+ displayName: string;
11
+ avatar?: string;
12
+ avatarUrl?: string;
13
+ }
14
+
15
+ /**
16
+ * Build an ordered array of QuickAccounts from a map and order list.
17
+ */
18
+ export const buildAccountsArray = (
19
+ accounts: Record<string, QuickAccount>,
20
+ order: string[]
21
+ ): QuickAccount[] => {
22
+ const result: QuickAccount[] = [];
23
+ for (const id of order) {
24
+ const account = accounts[id];
25
+ if (account) result.push(account);
26
+ }
27
+ return result;
28
+ };
29
+
30
+ /**
31
+ * Create a QuickAccount from user data returned by the API.
32
+ *
33
+ * @param sessionId - Session identifier
34
+ * @param userData - Raw user object from the API
35
+ * @param existingAccount - Previously cached account (to preserve avatarUrl if unchanged)
36
+ * @param getFileDownloadUrl - Function to generate avatar download URL from file ID
37
+ */
38
+ export const createQuickAccount = (
39
+ sessionId: string,
40
+ userData: {
41
+ name?: { full?: string; first?: string };
42
+ username?: string;
43
+ id?: string;
44
+ _id?: { toString(): string } | string;
45
+ avatar?: string;
46
+ },
47
+ existingAccount?: QuickAccount,
48
+ getFileDownloadUrl?: (fileId: string, variant: string) => string
49
+ ): QuickAccount => {
50
+ const displayName = userData.name?.full || userData.name?.first || userData.username || 'Account';
51
+ const userId = userData.id || (typeof userData._id === 'string' ? userData._id : userData._id?.toString());
52
+
53
+ // Preserve existing avatarUrl if avatar hasn't changed (prevents image reload)
54
+ let avatarUrl: string | undefined;
55
+ if (existingAccount && existingAccount.avatar === userData.avatar && existingAccount.avatarUrl) {
56
+ avatarUrl = existingAccount.avatarUrl;
57
+ } else if (userData.avatar && getFileDownloadUrl) {
58
+ avatarUrl = getFileDownloadUrl(userData.avatar, 'thumb');
59
+ }
60
+
61
+ return {
62
+ sessionId,
63
+ userId,
64
+ username: userData.username || '',
65
+ displayName,
66
+ avatar: userData.avatar,
67
+ avatarUrl,
68
+ };
69
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Minimal interface for services that can update asset visibility.
3
+ * Kept loose to avoid mixin type-inference issues with the OxyServices class.
4
+ */
5
+ export interface AssetVisibilityService {
6
+ assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<unknown>;
7
+ }
8
+
9
+ /**
10
+ * Updates file visibility to public for avatar use.
11
+ * Logs non-404 errors to help debug upload issues.
12
+ *
13
+ * @param fileId - The file ID to update visibility for
14
+ * @param oxyServices - OxyServices instance (or any object with assetUpdateVisibility)
15
+ * @param contextName - Context name for error logging
16
+ */
17
+ export async function updateAvatarVisibility(
18
+ fileId: string | undefined,
19
+ oxyServices: AssetVisibilityService,
20
+ contextName: string = 'AvatarUtils'
21
+ ): Promise<void> {
22
+ if (!fileId || fileId.startsWith('temp-')) {
23
+ return;
24
+ }
25
+
26
+ try {
27
+ await oxyServices.assetUpdateVisibility(fileId, 'public');
28
+ } catch (visError: unknown) {
29
+ // 404 is expected when asset doesn't exist yet — skip logging
30
+ const status = (visError instanceof Error && 'status' in visError)
31
+ ? (visError as Error & { status: number }).status
32
+ : undefined;
33
+ if (status !== 404) {
34
+ console.error(`[${contextName}] Failed to update avatar visibility for ${fileId}:`, visError);
35
+ }
36
+ }
37
+ }