@oxyhq/services 5.12.11 → 5.13.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.12.11",
3
+ "version": "5.13.0",
4
4
  "description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -1349,14 +1349,12 @@ export class OxyServices {
1349
1349
  /**
1350
1350
  * Link asset to an entity
1351
1351
  */
1352
- async assetLink(fileId: string, app: string, entityType: string, entityId: string, visibility?: 'private' | 'public' | 'unlisted'): Promise<any> {
1352
+ async assetLink(fileId: string, app: string, entityType: string, entityId: string, visibility?: 'private' | 'public' | 'unlisted', webhookUrl?: string): Promise<any> {
1353
1353
  try {
1354
- const res = await this.client.post(`/api/assets/${fileId}/links`, {
1355
- app,
1356
- entityType,
1357
- entityId,
1358
- visibility
1359
- });
1354
+ const body: any = { app, entityType, entityId };
1355
+ if (visibility) body.visibility = visibility;
1356
+ if (webhookUrl) body.webhookUrl = webhookUrl;
1357
+ const res = await this.client.post(`/api/assets/${fileId}/links`, body);
1360
1358
  return res.data;
1361
1359
  } catch (error) {
1362
1360
  throw this.handleError(error);
@@ -1508,6 +1506,92 @@ export class OxyServices {
1508
1506
  }
1509
1507
  }
1510
1508
 
1509
+ // ============================================================================
1510
+ // DEVELOPER API METHODS
1511
+ // ============================================================================
1512
+
1513
+ /**
1514
+ * Get developer apps for the current user
1515
+ */
1516
+ async getDeveloperApps(): Promise<any[]> {
1517
+ try {
1518
+ const res = await this.client.get('/api/developer/apps');
1519
+ return res.data;
1520
+ } catch (error) {
1521
+ throw this.handleError(error);
1522
+ }
1523
+ }
1524
+
1525
+ /**
1526
+ * Create a new developer app
1527
+ */
1528
+ async createDeveloperApp(data: {
1529
+ name: string;
1530
+ description?: string;
1531
+ webhookUrl?: string;
1532
+ scopes?: string[];
1533
+ }): Promise<any> {
1534
+ try {
1535
+ const res = await this.client.post('/api/developer/apps', data);
1536
+ return res.data;
1537
+ } catch (error) {
1538
+ throw this.handleError(error);
1539
+ }
1540
+ }
1541
+
1542
+ /**
1543
+ * Get a specific developer app
1544
+ */
1545
+ async getDeveloperApp(appId: string): Promise<any> {
1546
+ try {
1547
+ const res = await this.client.get(`/api/developer/apps/${appId}`);
1548
+ return res.data;
1549
+ } catch (error) {
1550
+ throw this.handleError(error);
1551
+ }
1552
+ }
1553
+
1554
+ /**
1555
+ * Update a developer app
1556
+ */
1557
+ async updateDeveloperApp(appId: string, data: {
1558
+ name?: string;
1559
+ description?: string;
1560
+ webhookUrl?: string;
1561
+ scopes?: string[];
1562
+ }): Promise<any> {
1563
+ try {
1564
+ const res = await this.client.patch(`/api/developer/apps/${appId}`, data);
1565
+ return res.data;
1566
+ } catch (error) {
1567
+ throw this.handleError(error);
1568
+ }
1569
+ }
1570
+
1571
+ /**
1572
+ * Regenerate API secret for a developer app
1573
+ */
1574
+ async regenerateDeveloperAppSecret(appId: string): Promise<any> {
1575
+ try {
1576
+ const res = await this.client.post(`/api/developer/apps/${appId}/regenerate-secret`);
1577
+ return res.data;
1578
+ } catch (error) {
1579
+ throw this.handleError(error);
1580
+ }
1581
+ }
1582
+
1583
+ /**
1584
+ * Delete a developer app
1585
+ */
1586
+ async deleteDeveloperApp(appId: string): Promise<any> {
1587
+ try {
1588
+ const res = await this.client.delete(`/api/developer/apps/${appId}`);
1589
+ return res.data;
1590
+ } catch (error) {
1591
+ throw this.handleError(error);
1592
+ }
1593
+ }
1594
+
1511
1595
  // ============================================================================
1512
1596
  // LOCATION METHODS
1513
1597
  // ============================================================================
@@ -55,6 +55,16 @@ export interface FileManagementScreenProps extends BaseScreenProps {
55
55
  * Useful for third-party apps that want files to be public (e.g., GIF selector)
56
56
  */
57
57
  defaultVisibility?: 'private' | 'public' | 'unlisted';
58
+ /**
59
+ * Link context for tracking file usage by third-party apps
60
+ * When provided, selected files will be linked to this entity
61
+ */
62
+ linkContext?: {
63
+ app: string; // App identifier (e.g., 'chat-app', 'post-composer')
64
+ entityType: string; // Type of entity (e.g., 'message', 'post', 'profile')
65
+ entityId: string; // Unique ID of the entity using this file
66
+ webhookUrl?: string; // Optional webhook URL to receive file events
67
+ };
58
68
  }
59
69
 
60
70
  // Add this helper function near the top (after imports):
@@ -79,6 +89,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
79
89
  afterSelect = 'close',
80
90
  allowUploadInSelectMode = true,
81
91
  defaultVisibility = 'private',
92
+ linkContext,
82
93
  }) => {
83
94
  const { user, oxyServices } = useOxy();
84
95
 
@@ -139,7 +150,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
139
150
  }
140
151
  }, [initialSelectedIds]);
141
152
 
142
- const toggleSelect = useCallback((file: FileMetadata) => {
153
+ const toggleSelect = useCallback(async (file: FileMetadata) => {
143
154
  if (!selectMode) return;
144
155
  if (disabledMimeTypes.length) {
145
156
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
@@ -148,6 +159,37 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
148
159
  return;
149
160
  }
150
161
  }
162
+
163
+ // Update file visibility if it differs from defaultVisibility
164
+ const fileVisibility = (file.metadata as any)?.visibility || 'private';
165
+ if (fileVisibility !== defaultVisibility) {
166
+ try {
167
+ await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
168
+ console.log(`Updated file ${file.id} visibility from ${fileVisibility} to ${defaultVisibility}`);
169
+ } catch (error) {
170
+ console.error('Failed to update file visibility:', error);
171
+ // Continue anyway - selection shouldn't fail if visibility update fails
172
+ }
173
+ }
174
+
175
+ // Link file to entity if linkContext is provided
176
+ if (linkContext) {
177
+ try {
178
+ await oxyServices.assetLink(
179
+ file.id,
180
+ linkContext.app,
181
+ linkContext.entityType,
182
+ linkContext.entityId,
183
+ defaultVisibility,
184
+ (linkContext as any).webhookUrl
185
+ );
186
+ console.log(`Linked file ${file.id} to ${linkContext.app}/${linkContext.entityType}/${linkContext.entityId}`);
187
+ } catch (error) {
188
+ console.error('Failed to link file:', error);
189
+ // Continue anyway - selection shouldn't fail if linking fails
190
+ }
191
+ }
192
+
151
193
  if (!multiSelect) {
152
194
  onSelect?.(file);
153
195
  if (afterSelect === 'back') {
@@ -171,16 +213,51 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
171
213
  }
172
214
  return next;
173
215
  });
174
- }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect]);
216
+ }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext]);
175
217
 
176
- const confirmMultiSelection = useCallback(() => {
218
+ const confirmMultiSelection = useCallback(async () => {
177
219
  if (!selectMode || !multiSelect) return;
178
220
  const map: Record<string, FileMetadata> = {};
179
221
  files.forEach(f => { map[f.id] = f; });
180
222
  const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
223
+
224
+ // Update visibility and link files if needed
225
+ const updatePromises = chosen.map(async (file) => {
226
+ // Update visibility if needed
227
+ const fileVisibility = (file.metadata as any)?.visibility || 'private';
228
+ if (fileVisibility !== defaultVisibility) {
229
+ try {
230
+ await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
231
+ console.log(`Updated file ${file.id} visibility from ${fileVisibility} to ${defaultVisibility}`);
232
+ } catch (error) {
233
+ console.error(`Failed to update visibility for ${file.id}:`, error);
234
+ }
235
+ }
236
+
237
+ // Link file to entity if linkContext provided
238
+ if (linkContext) {
239
+ try {
240
+ await oxyServices.assetLink(
241
+ file.id,
242
+ linkContext.app,
243
+ linkContext.entityType,
244
+ linkContext.entityId,
245
+ defaultVisibility,
246
+ (linkContext as any).webhookUrl
247
+ );
248
+ console.log(`Linked file ${file.id} to ${linkContext.app}/${linkContext.entityType}/${linkContext.entityId}`);
249
+ } catch (error) {
250
+ console.error(`Failed to link file ${file.id}:`, error);
251
+ }
252
+ }
253
+ });
254
+
255
+ // Wait for all updates (but don't block on failures)
256
+ await Promise.allSettled(updatePromises);
257
+
181
258
  onConfirmSelection?.(chosen);
182
259
  onClose?.();
183
- }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose]);
260
+ }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose, defaultVisibility, oxyServices, linkContext]);
184
261
 
185
262
  const endUpload = useCallback(() => {
186
263
  const started = uploadStartRef.current;