@oxyhq/services 5.12.10 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAgBjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAK3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAO5D,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACrC;AAOD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA00D7D,CAAC;AAszBF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAgBjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAK3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAO5D,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,EAAE;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAOD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA84D7D,CAAC;AAszBF,eAAe,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.12.10",
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
  // ============================================================================
@@ -50,11 +50,26 @@ export interface FileManagementScreenProps extends BaseScreenProps {
50
50
  */
51
51
  afterSelect?: 'close' | 'back' | 'none';
52
52
  allowUploadInSelectMode?: boolean;
53
+ /**
54
+ * Default visibility for uploaded files in this screen
55
+ * Useful for third-party apps that want files to be public (e.g., GIF selector)
56
+ */
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
+ };
53
68
  }
54
69
 
55
70
  // Add this helper function near the top (after imports):
56
- async function uploadFileRaw(file: File | Blob, userId: string, oxyServices: any) {
57
- return await oxyServices.uploadRawFile(file);
71
+ async function uploadFileRaw(file: File | Blob, userId: string, oxyServices: any, visibility?: 'private' | 'public' | 'unlisted') {
72
+ return await oxyServices.uploadRawFile(file, visibility);
58
73
  }
59
74
 
60
75
  const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
@@ -73,6 +88,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
73
88
  disabledMimeTypes = [],
74
89
  afterSelect = 'close',
75
90
  allowUploadInSelectMode = true,
91
+ defaultVisibility = 'private',
92
+ linkContext,
76
93
  }) => {
77
94
  const { user, oxyServices } = useOxy();
78
95
 
@@ -133,7 +150,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
133
150
  }
134
151
  }, [initialSelectedIds]);
135
152
 
136
- const toggleSelect = useCallback((file: FileMetadata) => {
153
+ const toggleSelect = useCallback(async (file: FileMetadata) => {
137
154
  if (!selectMode) return;
138
155
  if (disabledMimeTypes.length) {
139
156
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
@@ -142,6 +159,37 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
142
159
  return;
143
160
  }
144
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
+
145
193
  if (!multiSelect) {
146
194
  onSelect?.(file);
147
195
  if (afterSelect === 'back') {
@@ -165,16 +213,51 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
165
213
  }
166
214
  return next;
167
215
  });
168
- }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect]);
216
+ }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext]);
169
217
 
170
- const confirmMultiSelection = useCallback(() => {
218
+ const confirmMultiSelection = useCallback(async () => {
171
219
  if (!selectMode || !multiSelect) return;
172
220
  const map: Record<string, FileMetadata> = {};
173
221
  files.forEach(f => { map[f.id] = f; });
174
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
+
175
258
  onConfirmSelection?.(chosen);
176
259
  onClose?.();
177
- }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose]);
260
+ }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose, defaultVisibility, oxyServices, linkContext]);
178
261
 
179
262
  const endUpload = useCallback(() => {
180
263
  const started = uploadStartRef.current;
@@ -434,7 +517,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
434
517
  variants: [],
435
518
  };
436
519
  useFileStore.getState().addFile(optimisticFile, { prepend: true });
437
- const result = await uploadFileRaw(raw, targetUserId, oxyServices);
520
+ const result = await uploadFileRaw(raw, targetUserId, oxyServices, defaultVisibility);
438
521
  // Attempt to refresh file list incrementally – fetch single file metadata if API allows
439
522
  if (result?.file || result?.files?.[0]) {
440
523
  const f = result.file || result.files[0];