@scalemule/nextjs 0.1.7 → 0.1.9
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/README.md +61 -1
- package/dist/index.d.mts +68 -28
- package/dist/index.d.ts +68 -28
- package/dist/index.js +147 -5
- package/dist/index.mjs +147 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -132,7 +132,67 @@ function LoginPage() {
|
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
### `
|
|
135
|
+
### `useMedia()` — recommended for image / video / audio uploads
|
|
136
|
+
|
|
137
|
+
One hook covers every MIME type, defaults to private, and auto-routes images
|
|
138
|
+
through the photo pipeline (optimization, transform breakpoints) and videos
|
|
139
|
+
through the video pipeline (HLS transcoding). Drop the result `file_id` into
|
|
140
|
+
a chat message or render it with `<ScaleMuleMedia />` and progressive
|
|
141
|
+
enhancement just works.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { useMedia, ScaleMuleMedia } from '@scalemule/nextjs'
|
|
145
|
+
|
|
146
|
+
function Composer({ conversationId }: { conversationId: string }) {
|
|
147
|
+
const { upload, uploading } = useMedia()
|
|
148
|
+
|
|
149
|
+
async function handleFile(file: File) {
|
|
150
|
+
const result = await upload(file)
|
|
151
|
+
// result.file_id is private by default; post the message with it.
|
|
152
|
+
await postMessage({ conversationId, attachments: [{ file_id: result.file_id }] })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return <input type="file" disabled={uploading} onChange={(e) => handleFile(e.target.files![0])} />
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function Attachment({ fileId, conversationId, mimeType }: {
|
|
159
|
+
fileId: string; conversationId: string; mimeType: string
|
|
160
|
+
}) {
|
|
161
|
+
// <ScaleMuleMedia /> uses useFileStatus internally; with conversationId
|
|
162
|
+
// it subscribes to the conversation channel for live optimize/transcode
|
|
163
|
+
// updates instead of polling.
|
|
164
|
+
return (
|
|
165
|
+
<ScaleMuleMedia
|
|
166
|
+
fileId={fileId}
|
|
167
|
+
mimeType={mimeType}
|
|
168
|
+
conversationId={conversationId}
|
|
169
|
+
/>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
See [`docs/MEDIA-UPLOADS.md`](https://github.com/scalemule/scalemule-repos/blob/main/docs/MEDIA-UPLOADS.md)
|
|
175
|
+
for the full decision tree (which hook for which app, fast/safe policy modes,
|
|
176
|
+
private vs public visibility, anti-patterns).
|
|
177
|
+
|
|
178
|
+
### `useFileStatus()` — read-side progressive enhancement
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { useFileStatus } from '@scalemule/nextjs'
|
|
182
|
+
|
|
183
|
+
// Chat surface — subscribe to the conversation channel for push updates.
|
|
184
|
+
const { status, isReady } = useFileStatus({ fileId, conversationId })
|
|
185
|
+
|
|
186
|
+
// Non-chat — pull-only with optional polling.
|
|
187
|
+
const { status, isReady } = useFileStatus({ fileId, pollIntervalMs: 2000 })
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `useContent()` — generic file uploads (legacy)
|
|
191
|
+
|
|
192
|
+
> **Deprecated for chat / progressive media.** Use `useMedia()` instead.
|
|
193
|
+
> `useContent()` remains available for plain non-image/video file lists
|
|
194
|
+
> (documents, downloads). It does not auto-route through the photo/video
|
|
195
|
+
> pipelines and does not enforce private-by-default for chat surfaces.
|
|
136
196
|
|
|
137
197
|
```tsx
|
|
138
198
|
import { useContent } from '@scalemule/nextjs'
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ApiError, RealtimeService, StorageService, PhotoService, VideoService, FileStatus } from '@scalemule/sdk';
|
|
2
|
+
import { ApiError, RealtimeService, StorageService, PhotoService, VideoService, AudioService, FileStatus } from '@scalemule/sdk';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { ReactNode, ReactElement } from 'react';
|
|
5
5
|
import { MoneyClient } from '@scalemule/money';
|
|
@@ -164,6 +164,8 @@ interface ScaleMuleContextValue {
|
|
|
164
164
|
photo: PhotoService;
|
|
165
165
|
/** Base SDK video service — exposed for `useMedia()` and `video.uploadViaStorage()` */
|
|
166
166
|
video: VideoService;
|
|
167
|
+
/** Base SDK audio service — exposed for `useMedia()` and `audio.uploadViaStorage()` */
|
|
168
|
+
audio: AudioService;
|
|
167
169
|
/**
|
|
168
170
|
* Default media policy for `useMedia()` calls. Set via
|
|
169
171
|
* `<ScaleMuleProvider mediaPolicy="…">`; per-call overrides win.
|
|
@@ -322,25 +324,39 @@ interface UseContentOptions {
|
|
|
322
324
|
*/
|
|
323
325
|
declare function useContent(options?: UseContentOptions): UseContentReturn;
|
|
324
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Conversation kinds recognized by the chat realtime channel naming scheme.
|
|
329
|
+
* Matches `broadcast_to_conversation` in `ms/scalemule-chat/src/realtime.rs`.
|
|
330
|
+
*/
|
|
331
|
+
type ConversationKind = 'standard' | 'large_room' | 'broadcast' | 'support';
|
|
325
332
|
interface UseFileStatusOptions {
|
|
326
333
|
/** Storage file_id to read status for. */
|
|
327
334
|
fileId: string | null | undefined;
|
|
328
335
|
/**
|
|
329
336
|
* Optional poll interval in milliseconds. If set, the hook re-fetches
|
|
330
|
-
* status every `pollIntervalMs
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* Polling stops automatically once `scan.status === 'clean'` AND the
|
|
334
|
-
* caller's expected pipeline is done (`urls.optimized` returns 200 for
|
|
335
|
-
* images, `urls.hls` returns 200 for videos). Today the hook can only
|
|
336
|
-
* detect scan clean — broader pipeline-done detection lands when Phase 3
|
|
337
|
-
* enriches the optimize/transcode response fields.
|
|
337
|
+
* status every `pollIntervalMs` until scan goes clean. Useful for
|
|
338
|
+
* non-chat surfaces or as a fallback alongside `conversationId` push.
|
|
338
339
|
*/
|
|
339
340
|
pollIntervalMs?: number | null;
|
|
341
|
+
/** Disable the hook (don't fetch). Useful when `fileId` is conditional. */
|
|
342
|
+
disabled?: boolean;
|
|
340
343
|
/**
|
|
341
|
-
*
|
|
344
|
+
* Chat-surface push variant: subscribe to the conversation's realtime
|
|
345
|
+
* channel and refresh when a `file_status_changed` event arrives for
|
|
346
|
+
* `fileId`. Auth rides the existing per-conversation channel ACL.
|
|
342
347
|
*/
|
|
343
|
-
|
|
348
|
+
conversationId?: string | null;
|
|
349
|
+
/**
|
|
350
|
+
* Conversation kind, controls the channel name prefix:
|
|
351
|
+
* - `standard` (default) → `conversation:{id}`
|
|
352
|
+
* - `large_room` → `conversation:lr:{id}`
|
|
353
|
+
* - `broadcast` → `conversation:bc:{id}`
|
|
354
|
+
* - `support` → `conversation:support:{id}`
|
|
355
|
+
*
|
|
356
|
+
* If you only have a `messageId`, look up the conversation first via
|
|
357
|
+
* `client.chat.getMessage(messageId)` and pass the result here.
|
|
358
|
+
*/
|
|
359
|
+
conversationKind?: ConversationKind;
|
|
344
360
|
}
|
|
345
361
|
interface UseFileStatusReturn {
|
|
346
362
|
/** The latest status response, or null on first render / disabled. */
|
|
@@ -360,29 +376,39 @@ interface UseFileStatusReturn {
|
|
|
360
376
|
refresh: () => Promise<void>;
|
|
361
377
|
}
|
|
362
378
|
/**
|
|
363
|
-
* Subscribes to {@link FileStatus} for a single file.
|
|
364
|
-
* pull-only hook — single fetch by default, optional polling.
|
|
379
|
+
* Subscribes to {@link FileStatus} for a single file.
|
|
365
380
|
*
|
|
366
|
-
*
|
|
367
|
-
* for chat surfaces — `useFileStatus({ messageId })` will subscribe to
|
|
368
|
-
* `file.status` events on the existing per-conversation realtime channel
|
|
369
|
-
* via the `scalemule-chat` translation bridge (P5'). Until that lands,
|
|
370
|
-
* customers using this hook from chat surfaces should pass `pollIntervalMs`
|
|
371
|
-
* in the 1–3 second range while a media pipeline is expected to be running,
|
|
372
|
-
* then drop the polling once `isReady` is true.
|
|
381
|
+
* Three call shapes:
|
|
373
382
|
*
|
|
374
|
-
*
|
|
383
|
+
* 1. **Pull-only** — `useFileStatus({ fileId })` plus optional `pollIntervalMs`.
|
|
384
|
+
* One-shot fetch; useful for non-chat surfaces or static reads.
|
|
385
|
+
*
|
|
386
|
+
* 2. **Chat-surface push** — `useFileStatus({ fileId, conversationId, conversationKind? })`.
|
|
387
|
+
* Subscribes to the conversation channel and refreshes when
|
|
388
|
+
* `file_status_changed` arrives for this `fileId`. The chat service's
|
|
389
|
+
* media-status bridge fans photo/video lifecycle events into the per-conversation
|
|
390
|
+
* channel; the hook drops events for other files and dedupes against polling.
|
|
391
|
+
*
|
|
392
|
+
* 3. **Push + slow poll fallback** — combine `conversationId` with `pollIntervalMs`
|
|
393
|
+
* if you want belt-and-suspenders for environments where the websocket may drop.
|
|
394
|
+
*
|
|
395
|
+
* The `surface: 'profile'` push variant (private-user channel) is deferred until
|
|
396
|
+
* the realtime SDK exposes private-channel subscription by user.
|
|
397
|
+
*
|
|
398
|
+
* @example Chat surface (push)
|
|
375
399
|
* ```tsx
|
|
376
|
-
* function ChatImage({ fileId }: { fileId: string }) {
|
|
377
|
-
* const { status, isReady } = useFileStatus({
|
|
378
|
-
* fileId,
|
|
379
|
-
* pollIntervalMs: 2000,
|
|
380
|
-
* });
|
|
400
|
+
* function ChatImage({ fileId, conversationId }: { fileId: string; conversationId: string }) {
|
|
401
|
+
* const { status, isReady } = useFileStatus({ fileId, conversationId });
|
|
381
402
|
* if (!isReady) return <div>Scanning…</div>;
|
|
382
403
|
* const src = status?.urls.optimized ?? status?.urls.original;
|
|
383
404
|
* return <img src={src} />;
|
|
384
405
|
* }
|
|
385
406
|
* ```
|
|
407
|
+
*
|
|
408
|
+
* @example Non-chat surface (pull + poll)
|
|
409
|
+
* ```tsx
|
|
410
|
+
* const { status, isReady } = useFileStatus({ fileId, pollIntervalMs: 2000 });
|
|
411
|
+
* ```
|
|
386
412
|
*/
|
|
387
413
|
declare function useFileStatus(options: UseFileStatusOptions): UseFileStatusReturn;
|
|
388
414
|
|
|
@@ -411,9 +437,23 @@ interface ScaleMuleMediaProps {
|
|
|
411
437
|
/**
|
|
412
438
|
* Polling interval while waiting for the media pipeline to complete.
|
|
413
439
|
* Defaults to 2000ms; set to `null` to disable polling. Polling stops
|
|
414
|
-
* automatically once scan is clean.
|
|
440
|
+
* automatically once scan is clean. When `conversationId` is set the
|
|
441
|
+
* component receives push updates and you can usually pass `null`.
|
|
415
442
|
*/
|
|
416
443
|
pollIntervalMs?: number | null;
|
|
444
|
+
/**
|
|
445
|
+
* Chat-surface push: when set, the underlying `useFileStatus` hook
|
|
446
|
+
* subscribes to the conversation's realtime channel and refreshes on
|
|
447
|
+
* `file_status_changed` events for this `fileId`. See `useFileStatus`
|
|
448
|
+
* docs for channel naming and bridge behaviour.
|
|
449
|
+
*/
|
|
450
|
+
conversationId?: string | null;
|
|
451
|
+
/**
|
|
452
|
+
* Conversation kind, controls the channel name prefix
|
|
453
|
+
* (`conversation:{id}` vs `conversation:lr|bc|support:{id}`).
|
|
454
|
+
* Default is `'standard'`.
|
|
455
|
+
*/
|
|
456
|
+
conversationKind?: ConversationKind;
|
|
417
457
|
/**
|
|
418
458
|
* Render a custom placeholder while waiting for scan / upload. Defaults
|
|
419
459
|
* to a tiny "Loading…" div. Receives the current FileStatus (or null).
|
|
@@ -948,4 +988,4 @@ declare function createSafeLogger(prefix: string): {
|
|
|
948
988
|
error: (message: string, data?: unknown) => void;
|
|
949
989
|
};
|
|
950
990
|
|
|
951
|
-
export { ApiError$1 as ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, type FeedbackItem, type FeedbackItemInput, type FeedbackPriority, type FeedbackStatus, type FeedbackType, FeedbackWidget, type FeedbackWidgetConfig, type FeedbackWidgetProps, ListFilesParams, LoginResponse, type MediaPolicy, type MediaUploadResult, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleMedia, type ScaleMuleMediaProps, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeedbackOptions, type UseFeedbackResult, type UseFileStatusOptions, type UseFileStatusReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UseMediaReturn, type UseMediaUploadOptions, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
|
991
|
+
export { ApiError$1 as ApiError, type ConversationKind, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, type FeedbackItem, type FeedbackItemInput, type FeedbackPriority, type FeedbackStatus, type FeedbackType, FeedbackWidget, type FeedbackWidgetConfig, type FeedbackWidgetProps, ListFilesParams, LoginResponse, type MediaPolicy, type MediaUploadResult, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleMedia, type ScaleMuleMediaProps, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeedbackOptions, type UseFeedbackResult, type UseFileStatusOptions, type UseFileStatusReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UseMediaReturn, type UseMediaUploadOptions, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ApiError, RealtimeService, StorageService, PhotoService, VideoService, FileStatus } from '@scalemule/sdk';
|
|
2
|
+
import { ApiError, RealtimeService, StorageService, PhotoService, VideoService, AudioService, FileStatus } from '@scalemule/sdk';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { ReactNode, ReactElement } from 'react';
|
|
5
5
|
import { MoneyClient } from '@scalemule/money';
|
|
@@ -164,6 +164,8 @@ interface ScaleMuleContextValue {
|
|
|
164
164
|
photo: PhotoService;
|
|
165
165
|
/** Base SDK video service — exposed for `useMedia()` and `video.uploadViaStorage()` */
|
|
166
166
|
video: VideoService;
|
|
167
|
+
/** Base SDK audio service — exposed for `useMedia()` and `audio.uploadViaStorage()` */
|
|
168
|
+
audio: AudioService;
|
|
167
169
|
/**
|
|
168
170
|
* Default media policy for `useMedia()` calls. Set via
|
|
169
171
|
* `<ScaleMuleProvider mediaPolicy="…">`; per-call overrides win.
|
|
@@ -322,25 +324,39 @@ interface UseContentOptions {
|
|
|
322
324
|
*/
|
|
323
325
|
declare function useContent(options?: UseContentOptions): UseContentReturn;
|
|
324
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Conversation kinds recognized by the chat realtime channel naming scheme.
|
|
329
|
+
* Matches `broadcast_to_conversation` in `ms/scalemule-chat/src/realtime.rs`.
|
|
330
|
+
*/
|
|
331
|
+
type ConversationKind = 'standard' | 'large_room' | 'broadcast' | 'support';
|
|
325
332
|
interface UseFileStatusOptions {
|
|
326
333
|
/** Storage file_id to read status for. */
|
|
327
334
|
fileId: string | null | undefined;
|
|
328
335
|
/**
|
|
329
336
|
* Optional poll interval in milliseconds. If set, the hook re-fetches
|
|
330
|
-
* status every `pollIntervalMs
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* Polling stops automatically once `scan.status === 'clean'` AND the
|
|
334
|
-
* caller's expected pipeline is done (`urls.optimized` returns 200 for
|
|
335
|
-
* images, `urls.hls` returns 200 for videos). Today the hook can only
|
|
336
|
-
* detect scan clean — broader pipeline-done detection lands when Phase 3
|
|
337
|
-
* enriches the optimize/transcode response fields.
|
|
337
|
+
* status every `pollIntervalMs` until scan goes clean. Useful for
|
|
338
|
+
* non-chat surfaces or as a fallback alongside `conversationId` push.
|
|
338
339
|
*/
|
|
339
340
|
pollIntervalMs?: number | null;
|
|
341
|
+
/** Disable the hook (don't fetch). Useful when `fileId` is conditional. */
|
|
342
|
+
disabled?: boolean;
|
|
340
343
|
/**
|
|
341
|
-
*
|
|
344
|
+
* Chat-surface push variant: subscribe to the conversation's realtime
|
|
345
|
+
* channel and refresh when a `file_status_changed` event arrives for
|
|
346
|
+
* `fileId`. Auth rides the existing per-conversation channel ACL.
|
|
342
347
|
*/
|
|
343
|
-
|
|
348
|
+
conversationId?: string | null;
|
|
349
|
+
/**
|
|
350
|
+
* Conversation kind, controls the channel name prefix:
|
|
351
|
+
* - `standard` (default) → `conversation:{id}`
|
|
352
|
+
* - `large_room` → `conversation:lr:{id}`
|
|
353
|
+
* - `broadcast` → `conversation:bc:{id}`
|
|
354
|
+
* - `support` → `conversation:support:{id}`
|
|
355
|
+
*
|
|
356
|
+
* If you only have a `messageId`, look up the conversation first via
|
|
357
|
+
* `client.chat.getMessage(messageId)` and pass the result here.
|
|
358
|
+
*/
|
|
359
|
+
conversationKind?: ConversationKind;
|
|
344
360
|
}
|
|
345
361
|
interface UseFileStatusReturn {
|
|
346
362
|
/** The latest status response, or null on first render / disabled. */
|
|
@@ -360,29 +376,39 @@ interface UseFileStatusReturn {
|
|
|
360
376
|
refresh: () => Promise<void>;
|
|
361
377
|
}
|
|
362
378
|
/**
|
|
363
|
-
* Subscribes to {@link FileStatus} for a single file.
|
|
364
|
-
* pull-only hook — single fetch by default, optional polling.
|
|
379
|
+
* Subscribes to {@link FileStatus} for a single file.
|
|
365
380
|
*
|
|
366
|
-
*
|
|
367
|
-
* for chat surfaces — `useFileStatus({ messageId })` will subscribe to
|
|
368
|
-
* `file.status` events on the existing per-conversation realtime channel
|
|
369
|
-
* via the `scalemule-chat` translation bridge (P5'). Until that lands,
|
|
370
|
-
* customers using this hook from chat surfaces should pass `pollIntervalMs`
|
|
371
|
-
* in the 1–3 second range while a media pipeline is expected to be running,
|
|
372
|
-
* then drop the polling once `isReady` is true.
|
|
381
|
+
* Three call shapes:
|
|
373
382
|
*
|
|
374
|
-
*
|
|
383
|
+
* 1. **Pull-only** — `useFileStatus({ fileId })` plus optional `pollIntervalMs`.
|
|
384
|
+
* One-shot fetch; useful for non-chat surfaces or static reads.
|
|
385
|
+
*
|
|
386
|
+
* 2. **Chat-surface push** — `useFileStatus({ fileId, conversationId, conversationKind? })`.
|
|
387
|
+
* Subscribes to the conversation channel and refreshes when
|
|
388
|
+
* `file_status_changed` arrives for this `fileId`. The chat service's
|
|
389
|
+
* media-status bridge fans photo/video lifecycle events into the per-conversation
|
|
390
|
+
* channel; the hook drops events for other files and dedupes against polling.
|
|
391
|
+
*
|
|
392
|
+
* 3. **Push + slow poll fallback** — combine `conversationId` with `pollIntervalMs`
|
|
393
|
+
* if you want belt-and-suspenders for environments where the websocket may drop.
|
|
394
|
+
*
|
|
395
|
+
* The `surface: 'profile'` push variant (private-user channel) is deferred until
|
|
396
|
+
* the realtime SDK exposes private-channel subscription by user.
|
|
397
|
+
*
|
|
398
|
+
* @example Chat surface (push)
|
|
375
399
|
* ```tsx
|
|
376
|
-
* function ChatImage({ fileId }: { fileId: string }) {
|
|
377
|
-
* const { status, isReady } = useFileStatus({
|
|
378
|
-
* fileId,
|
|
379
|
-
* pollIntervalMs: 2000,
|
|
380
|
-
* });
|
|
400
|
+
* function ChatImage({ fileId, conversationId }: { fileId: string; conversationId: string }) {
|
|
401
|
+
* const { status, isReady } = useFileStatus({ fileId, conversationId });
|
|
381
402
|
* if (!isReady) return <div>Scanning…</div>;
|
|
382
403
|
* const src = status?.urls.optimized ?? status?.urls.original;
|
|
383
404
|
* return <img src={src} />;
|
|
384
405
|
* }
|
|
385
406
|
* ```
|
|
407
|
+
*
|
|
408
|
+
* @example Non-chat surface (pull + poll)
|
|
409
|
+
* ```tsx
|
|
410
|
+
* const { status, isReady } = useFileStatus({ fileId, pollIntervalMs: 2000 });
|
|
411
|
+
* ```
|
|
386
412
|
*/
|
|
387
413
|
declare function useFileStatus(options: UseFileStatusOptions): UseFileStatusReturn;
|
|
388
414
|
|
|
@@ -411,9 +437,23 @@ interface ScaleMuleMediaProps {
|
|
|
411
437
|
/**
|
|
412
438
|
* Polling interval while waiting for the media pipeline to complete.
|
|
413
439
|
* Defaults to 2000ms; set to `null` to disable polling. Polling stops
|
|
414
|
-
* automatically once scan is clean.
|
|
440
|
+
* automatically once scan is clean. When `conversationId` is set the
|
|
441
|
+
* component receives push updates and you can usually pass `null`.
|
|
415
442
|
*/
|
|
416
443
|
pollIntervalMs?: number | null;
|
|
444
|
+
/**
|
|
445
|
+
* Chat-surface push: when set, the underlying `useFileStatus` hook
|
|
446
|
+
* subscribes to the conversation's realtime channel and refreshes on
|
|
447
|
+
* `file_status_changed` events for this `fileId`. See `useFileStatus`
|
|
448
|
+
* docs for channel naming and bridge behaviour.
|
|
449
|
+
*/
|
|
450
|
+
conversationId?: string | null;
|
|
451
|
+
/**
|
|
452
|
+
* Conversation kind, controls the channel name prefix
|
|
453
|
+
* (`conversation:{id}` vs `conversation:lr|bc|support:{id}`).
|
|
454
|
+
* Default is `'standard'`.
|
|
455
|
+
*/
|
|
456
|
+
conversationKind?: ConversationKind;
|
|
417
457
|
/**
|
|
418
458
|
* Render a custom placeholder while waiting for scan / upload. Defaults
|
|
419
459
|
* to a tiny "Loading…" div. Receives the current FileStatus (or null).
|
|
@@ -948,4 +988,4 @@ declare function createSafeLogger(prefix: string): {
|
|
|
948
988
|
error: (message: string, data?: unknown) => void;
|
|
949
989
|
};
|
|
950
990
|
|
|
951
|
-
export { ApiError$1 as ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, type FeedbackItem, type FeedbackItemInput, type FeedbackPriority, type FeedbackStatus, type FeedbackType, FeedbackWidget, type FeedbackWidgetConfig, type FeedbackWidgetProps, ListFilesParams, LoginResponse, type MediaPolicy, type MediaUploadResult, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleMedia, type ScaleMuleMediaProps, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeedbackOptions, type UseFeedbackResult, type UseFileStatusOptions, type UseFileStatusReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UseMediaReturn, type UseMediaUploadOptions, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
|
991
|
+
export { ApiError$1 as ApiError, type ConversationKind, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, type FeedbackItem, type FeedbackItemInput, type FeedbackPriority, type FeedbackStatus, type FeedbackType, FeedbackWidget, type FeedbackWidgetConfig, type FeedbackWidgetProps, ListFilesParams, LoginResponse, type MediaPolicy, type MediaUploadResult, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleMedia, type ScaleMuleMediaProps, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeedbackOptions, type UseFeedbackResult, type UseFileStatusOptions, type UseFileStatusReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UseMediaReturn, type UseMediaUploadOptions, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
package/dist/index.js
CHANGED
|
@@ -2727,6 +2727,25 @@ var StorageService = class extends ServiceModule {
|
|
|
2727
2727
|
async getInfo(fileId, options) {
|
|
2728
2728
|
return this._get(`/files/${fileId}/info`, options);
|
|
2729
2729
|
}
|
|
2730
|
+
/**
|
|
2731
|
+
* Get the calling app's storage + media settings — content policy,
|
|
2732
|
+
* retention, max upload size, and the per-app `media_policy`.
|
|
2733
|
+
*
|
|
2734
|
+
* `media_policy` drives release-gating in the SDK upload helpers:
|
|
2735
|
+
* `fast_trusted` / `safe_visible` resolve immediately on upload; the
|
|
2736
|
+
* `safe_public` / `moderated` / `compliance` modes await pipeline
|
|
2737
|
+
* completion before resolving the upload promise.
|
|
2738
|
+
*/
|
|
2739
|
+
async getSettings(options) {
|
|
2740
|
+
return this._get("/settings", options);
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Update the calling app's storage + media settings. Admin-only on the
|
|
2744
|
+
* platform side (callers without the right role get 403).
|
|
2745
|
+
*/
|
|
2746
|
+
async updateSettings(settings, options) {
|
|
2747
|
+
return this.put("/settings", settings, options);
|
|
2748
|
+
}
|
|
2730
2749
|
/**
|
|
2731
2750
|
* Aggregate status for a file — single call returns scan + reserved
|
|
2732
2751
|
* optimize/transcode slots + the canonical view URL paths for image
|
|
@@ -5678,6 +5697,72 @@ var PhotoService = class extends ServiceModule {
|
|
|
5678
5697
|
return this.get(id);
|
|
5679
5698
|
}
|
|
5680
5699
|
};
|
|
5700
|
+
var AudioService = class extends ServiceModule {
|
|
5701
|
+
/**
|
|
5702
|
+
* @param storage Required for {@link uploadViaStorage}. Wired up by the
|
|
5703
|
+
* top-level {@link ScaleMule} constructor — most call sites should not
|
|
5704
|
+
* instantiate `AudioService` directly.
|
|
5705
|
+
*/
|
|
5706
|
+
constructor(client, storage) {
|
|
5707
|
+
super(client);
|
|
5708
|
+
this.storage = storage;
|
|
5709
|
+
this.basePath = "/v1/audios";
|
|
5710
|
+
}
|
|
5711
|
+
/**
|
|
5712
|
+
* Register a storage-uploaded audio asset with the audio service.
|
|
5713
|
+
* Idempotent — re-calling with the same `file_id` returns the existing
|
|
5714
|
+
* record.
|
|
5715
|
+
*/
|
|
5716
|
+
async register(args, options) {
|
|
5717
|
+
return this.post("/register", { file_id: args.fileId, sm_user_id: args.userId }, options);
|
|
5718
|
+
}
|
|
5719
|
+
/**
|
|
5720
|
+
* Upload a file to storage (browser → S3 direct, private, uncompressed)
|
|
5721
|
+
* and register it with the audio service.
|
|
5722
|
+
*
|
|
5723
|
+
* Mirrors `photo.uploadViaStorage()` / `video.uploadViaStorage()`. If
|
|
5724
|
+
* `register()` fails after a successful storage upload, the file is *not*
|
|
5725
|
+
* lost — the returned `file_id` is still valid as a generic private storage
|
|
5726
|
+
* file. The SDK logs a warning and returns `audio_id: null`.
|
|
5727
|
+
*/
|
|
5728
|
+
async uploadViaStorage(file, uploadOptions, requestOptions) {
|
|
5729
|
+
const uploadResult = await this.storage.uploadPrivate(file, {
|
|
5730
|
+
filename: uploadOptions?.filename,
|
|
5731
|
+
metadata: uploadOptions?.metadata,
|
|
5732
|
+
onProgress: uploadOptions?.onProgress,
|
|
5733
|
+
signal: uploadOptions?.signal
|
|
5734
|
+
});
|
|
5735
|
+
if (uploadResult.error || !uploadResult.data) {
|
|
5736
|
+
return { data: null, error: uploadResult.error };
|
|
5737
|
+
}
|
|
5738
|
+
const fileInfo = uploadResult.data;
|
|
5739
|
+
const fileId = fileInfo.id;
|
|
5740
|
+
const originalViewUrl = fileInfo.url ?? null;
|
|
5741
|
+
const registerResult = await this.register({ fileId, userId: uploadOptions?.userId }, requestOptions);
|
|
5742
|
+
if (registerResult.error || !registerResult.data) {
|
|
5743
|
+
console.warn(
|
|
5744
|
+
"[scalemule-sdk] audio.register() failed after storage upload; typed audio metadata unavailable.",
|
|
5745
|
+
registerResult.error
|
|
5746
|
+
);
|
|
5747
|
+
return {
|
|
5748
|
+
data: {
|
|
5749
|
+
file_id: fileId,
|
|
5750
|
+
audio_id: null,
|
|
5751
|
+
original_view_url: originalViewUrl
|
|
5752
|
+
},
|
|
5753
|
+
error: null
|
|
5754
|
+
};
|
|
5755
|
+
}
|
|
5756
|
+
return {
|
|
5757
|
+
data: {
|
|
5758
|
+
file_id: fileId,
|
|
5759
|
+
audio_id: registerResult.data.audio_id,
|
|
5760
|
+
original_view_url: originalViewUrl
|
|
5761
|
+
},
|
|
5762
|
+
error: null
|
|
5763
|
+
};
|
|
5764
|
+
}
|
|
5765
|
+
};
|
|
5681
5766
|
var DeadLetterApi = class extends ServiceModule {
|
|
5682
5767
|
constructor() {
|
|
5683
5768
|
super(...arguments);
|
|
@@ -6497,6 +6582,7 @@ var ScaleMule = class {
|
|
|
6497
6582
|
this.graph = new GraphService(this._client);
|
|
6498
6583
|
this.functions = new FunctionsService(this._client);
|
|
6499
6584
|
this.photo = new PhotoService(this._client, this.storage);
|
|
6585
|
+
this.audio = new AudioService(this._client, this.storage);
|
|
6500
6586
|
this.flagContent = new FlagContentService(this._client);
|
|
6501
6587
|
this.creatorMaker = new CreatorMakerService(this._client);
|
|
6502
6588
|
this.compliance = new ComplianceService(this._client);
|
|
@@ -7585,6 +7671,7 @@ function ScaleMuleProvider({
|
|
|
7585
7671
|
storage: baseClient.storage,
|
|
7586
7672
|
photo: baseClient.photo,
|
|
7587
7673
|
video: baseClient.video,
|
|
7674
|
+
audio: baseClient.audio,
|
|
7588
7675
|
mediaPolicy,
|
|
7589
7676
|
user,
|
|
7590
7677
|
setUser: handleSetUser,
|
|
@@ -8899,7 +8986,7 @@ function useContent(options = {}) {
|
|
|
8899
8986
|
);
|
|
8900
8987
|
}
|
|
8901
8988
|
function useMedia() {
|
|
8902
|
-
const { storage, photo, video, mediaPolicy: providerDefaultPolicy } = useScaleMule();
|
|
8989
|
+
const { storage, photo, video, audio, mediaPolicy: providerDefaultPolicy } = useScaleMule();
|
|
8903
8990
|
const [uploading, setUploading] = React.useState(false);
|
|
8904
8991
|
const [error, setError] = React.useState(null);
|
|
8905
8992
|
const upload = React.useCallback(
|
|
@@ -8953,6 +9040,21 @@ function useMedia() {
|
|
|
8953
9040
|
is_public: false
|
|
8954
9041
|
};
|
|
8955
9042
|
}
|
|
9043
|
+
if (mimeType.startsWith("audio/") && !isPublic) {
|
|
9044
|
+
const r2 = await audio.uploadViaStorage(file, sharedOpts);
|
|
9045
|
+
if (r2.error || !r2.data) {
|
|
9046
|
+
throw r2.error ?? { code: "upload_error", message: "Upload failed", status: 0 };
|
|
9047
|
+
}
|
|
9048
|
+
return {
|
|
9049
|
+
file_id: r2.data.file_id,
|
|
9050
|
+
photo_id: null,
|
|
9051
|
+
original_view_url: r2.data.original_view_url,
|
|
9052
|
+
optimized_url_promise: Promise.resolve(null),
|
|
9053
|
+
hls_url_promise: Promise.resolve(null),
|
|
9054
|
+
mime_type: mimeType,
|
|
9055
|
+
is_public: false
|
|
9056
|
+
};
|
|
9057
|
+
}
|
|
8956
9058
|
if (mimeType.startsWith("image/") && isPublic) {
|
|
8957
9059
|
const r2 = await storage.upload(file, {
|
|
8958
9060
|
...sharedOpts,
|
|
@@ -8995,7 +9097,7 @@ function useMedia() {
|
|
|
8995
9097
|
setUploading(false);
|
|
8996
9098
|
}
|
|
8997
9099
|
},
|
|
8998
|
-
[storage, photo, video, providerDefaultPolicy]
|
|
9100
|
+
[storage, photo, video, audio, providerDefaultPolicy]
|
|
8999
9101
|
);
|
|
9000
9102
|
const cancelUpload = React.useCallback(
|
|
9001
9103
|
async (fileId) => {
|
|
@@ -9016,9 +9118,28 @@ function useMedia() {
|
|
|
9016
9118
|
);
|
|
9017
9119
|
return { upload, cancelUpload, error, uploading };
|
|
9018
9120
|
}
|
|
9121
|
+
function conversationChannel(kind, id) {
|
|
9122
|
+
switch (kind) {
|
|
9123
|
+
case "large_room":
|
|
9124
|
+
return `conversation:lr:${id}`;
|
|
9125
|
+
case "broadcast":
|
|
9126
|
+
return `conversation:bc:${id}`;
|
|
9127
|
+
case "support":
|
|
9128
|
+
return `conversation:support:${id}`;
|
|
9129
|
+
case "standard":
|
|
9130
|
+
default:
|
|
9131
|
+
return `conversation:${id}`;
|
|
9132
|
+
}
|
|
9133
|
+
}
|
|
9019
9134
|
function useFileStatus(options) {
|
|
9020
|
-
const { storage } = useScaleMule();
|
|
9021
|
-
const {
|
|
9135
|
+
const { storage, realtime } = useScaleMule();
|
|
9136
|
+
const {
|
|
9137
|
+
fileId,
|
|
9138
|
+
pollIntervalMs = null,
|
|
9139
|
+
disabled = false,
|
|
9140
|
+
conversationId = null,
|
|
9141
|
+
conversationKind = "standard"
|
|
9142
|
+
} = options;
|
|
9022
9143
|
const [status, setStatus] = React.useState(null);
|
|
9023
9144
|
const [loading, setLoading] = React.useState(false);
|
|
9024
9145
|
const [error, setError] = React.useState(null);
|
|
@@ -9057,6 +9178,20 @@ function useFileStatus(options) {
|
|
|
9057
9178
|
}, pollIntervalMs);
|
|
9058
9179
|
return () => clearInterval(id);
|
|
9059
9180
|
}, [pollIntervalMs, disabled, fileId, status?.scan.status, fetchStatus]);
|
|
9181
|
+
React.useEffect(() => {
|
|
9182
|
+
if (!conversationId || !fileId || disabled) return;
|
|
9183
|
+
const channel = conversationChannel(conversationKind, conversationId);
|
|
9184
|
+
const unsub = realtime.subscribe(channel, (data) => {
|
|
9185
|
+
if (typeof data !== "object" || data === null) return;
|
|
9186
|
+
const payload = data;
|
|
9187
|
+
const wrapped = "data" in payload && typeof payload.data === "object" && payload.data !== null ? payload.data : payload;
|
|
9188
|
+
const evt = typeof payload.event === "string" ? payload.event : void 0;
|
|
9189
|
+
if (evt && evt !== "file_status_changed") return;
|
|
9190
|
+
if (wrapped.file_id !== fileId) return;
|
|
9191
|
+
void fetchStatus();
|
|
9192
|
+
});
|
|
9193
|
+
return unsub;
|
|
9194
|
+
}, [realtime, conversationId, conversationKind, fileId, disabled, fetchStatus]);
|
|
9060
9195
|
const isReady = status?.scan.status === "clean";
|
|
9061
9196
|
return { status, loading, error, isReady, refresh: fetchStatus };
|
|
9062
9197
|
}
|
|
@@ -9071,11 +9206,18 @@ function ScaleMuleMedia(props) {
|
|
|
9071
9206
|
style,
|
|
9072
9207
|
alt,
|
|
9073
9208
|
pollIntervalMs = 2e3,
|
|
9209
|
+
conversationId = null,
|
|
9210
|
+
conversationKind,
|
|
9074
9211
|
renderPlaceholder,
|
|
9075
9212
|
renderBlocked,
|
|
9076
9213
|
renderOverride
|
|
9077
9214
|
} = props;
|
|
9078
|
-
const { status, isReady } = useFileStatus({
|
|
9215
|
+
const { status, isReady } = useFileStatus({
|
|
9216
|
+
fileId,
|
|
9217
|
+
pollIntervalMs,
|
|
9218
|
+
conversationId,
|
|
9219
|
+
conversationKind
|
|
9220
|
+
});
|
|
9079
9221
|
const isImage = mimeType.startsWith("image/");
|
|
9080
9222
|
const isVideo = mimeType.startsWith("video/");
|
|
9081
9223
|
const isAudio = mimeType.startsWith("audio/");
|
package/dist/index.mjs
CHANGED
|
@@ -2707,6 +2707,25 @@ var StorageService = class extends ServiceModule {
|
|
|
2707
2707
|
async getInfo(fileId, options) {
|
|
2708
2708
|
return this._get(`/files/${fileId}/info`, options);
|
|
2709
2709
|
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Get the calling app's storage + media settings — content policy,
|
|
2712
|
+
* retention, max upload size, and the per-app `media_policy`.
|
|
2713
|
+
*
|
|
2714
|
+
* `media_policy` drives release-gating in the SDK upload helpers:
|
|
2715
|
+
* `fast_trusted` / `safe_visible` resolve immediately on upload; the
|
|
2716
|
+
* `safe_public` / `moderated` / `compliance` modes await pipeline
|
|
2717
|
+
* completion before resolving the upload promise.
|
|
2718
|
+
*/
|
|
2719
|
+
async getSettings(options) {
|
|
2720
|
+
return this._get("/settings", options);
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Update the calling app's storage + media settings. Admin-only on the
|
|
2724
|
+
* platform side (callers without the right role get 403).
|
|
2725
|
+
*/
|
|
2726
|
+
async updateSettings(settings, options) {
|
|
2727
|
+
return this.put("/settings", settings, options);
|
|
2728
|
+
}
|
|
2710
2729
|
/**
|
|
2711
2730
|
* Aggregate status for a file — single call returns scan + reserved
|
|
2712
2731
|
* optimize/transcode slots + the canonical view URL paths for image
|
|
@@ -5658,6 +5677,72 @@ var PhotoService = class extends ServiceModule {
|
|
|
5658
5677
|
return this.get(id);
|
|
5659
5678
|
}
|
|
5660
5679
|
};
|
|
5680
|
+
var AudioService = class extends ServiceModule {
|
|
5681
|
+
/**
|
|
5682
|
+
* @param storage Required for {@link uploadViaStorage}. Wired up by the
|
|
5683
|
+
* top-level {@link ScaleMule} constructor — most call sites should not
|
|
5684
|
+
* instantiate `AudioService` directly.
|
|
5685
|
+
*/
|
|
5686
|
+
constructor(client, storage) {
|
|
5687
|
+
super(client);
|
|
5688
|
+
this.storage = storage;
|
|
5689
|
+
this.basePath = "/v1/audios";
|
|
5690
|
+
}
|
|
5691
|
+
/**
|
|
5692
|
+
* Register a storage-uploaded audio asset with the audio service.
|
|
5693
|
+
* Idempotent — re-calling with the same `file_id` returns the existing
|
|
5694
|
+
* record.
|
|
5695
|
+
*/
|
|
5696
|
+
async register(args, options) {
|
|
5697
|
+
return this.post("/register", { file_id: args.fileId, sm_user_id: args.userId }, options);
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Upload a file to storage (browser → S3 direct, private, uncompressed)
|
|
5701
|
+
* and register it with the audio service.
|
|
5702
|
+
*
|
|
5703
|
+
* Mirrors `photo.uploadViaStorage()` / `video.uploadViaStorage()`. If
|
|
5704
|
+
* `register()` fails after a successful storage upload, the file is *not*
|
|
5705
|
+
* lost — the returned `file_id` is still valid as a generic private storage
|
|
5706
|
+
* file. The SDK logs a warning and returns `audio_id: null`.
|
|
5707
|
+
*/
|
|
5708
|
+
async uploadViaStorage(file, uploadOptions, requestOptions) {
|
|
5709
|
+
const uploadResult = await this.storage.uploadPrivate(file, {
|
|
5710
|
+
filename: uploadOptions?.filename,
|
|
5711
|
+
metadata: uploadOptions?.metadata,
|
|
5712
|
+
onProgress: uploadOptions?.onProgress,
|
|
5713
|
+
signal: uploadOptions?.signal
|
|
5714
|
+
});
|
|
5715
|
+
if (uploadResult.error || !uploadResult.data) {
|
|
5716
|
+
return { data: null, error: uploadResult.error };
|
|
5717
|
+
}
|
|
5718
|
+
const fileInfo = uploadResult.data;
|
|
5719
|
+
const fileId = fileInfo.id;
|
|
5720
|
+
const originalViewUrl = fileInfo.url ?? null;
|
|
5721
|
+
const registerResult = await this.register({ fileId, userId: uploadOptions?.userId }, requestOptions);
|
|
5722
|
+
if (registerResult.error || !registerResult.data) {
|
|
5723
|
+
console.warn(
|
|
5724
|
+
"[scalemule-sdk] audio.register() failed after storage upload; typed audio metadata unavailable.",
|
|
5725
|
+
registerResult.error
|
|
5726
|
+
);
|
|
5727
|
+
return {
|
|
5728
|
+
data: {
|
|
5729
|
+
file_id: fileId,
|
|
5730
|
+
audio_id: null,
|
|
5731
|
+
original_view_url: originalViewUrl
|
|
5732
|
+
},
|
|
5733
|
+
error: null
|
|
5734
|
+
};
|
|
5735
|
+
}
|
|
5736
|
+
return {
|
|
5737
|
+
data: {
|
|
5738
|
+
file_id: fileId,
|
|
5739
|
+
audio_id: registerResult.data.audio_id,
|
|
5740
|
+
original_view_url: originalViewUrl
|
|
5741
|
+
},
|
|
5742
|
+
error: null
|
|
5743
|
+
};
|
|
5744
|
+
}
|
|
5745
|
+
};
|
|
5661
5746
|
var DeadLetterApi = class extends ServiceModule {
|
|
5662
5747
|
constructor() {
|
|
5663
5748
|
super(...arguments);
|
|
@@ -6477,6 +6562,7 @@ var ScaleMule = class {
|
|
|
6477
6562
|
this.graph = new GraphService(this._client);
|
|
6478
6563
|
this.functions = new FunctionsService(this._client);
|
|
6479
6564
|
this.photo = new PhotoService(this._client, this.storage);
|
|
6565
|
+
this.audio = new AudioService(this._client, this.storage);
|
|
6480
6566
|
this.flagContent = new FlagContentService(this._client);
|
|
6481
6567
|
this.creatorMaker = new CreatorMakerService(this._client);
|
|
6482
6568
|
this.compliance = new ComplianceService(this._client);
|
|
@@ -7565,6 +7651,7 @@ function ScaleMuleProvider({
|
|
|
7565
7651
|
storage: baseClient.storage,
|
|
7566
7652
|
photo: baseClient.photo,
|
|
7567
7653
|
video: baseClient.video,
|
|
7654
|
+
audio: baseClient.audio,
|
|
7568
7655
|
mediaPolicy,
|
|
7569
7656
|
user,
|
|
7570
7657
|
setUser: handleSetUser,
|
|
@@ -8879,7 +8966,7 @@ function useContent(options = {}) {
|
|
|
8879
8966
|
);
|
|
8880
8967
|
}
|
|
8881
8968
|
function useMedia() {
|
|
8882
|
-
const { storage, photo, video, mediaPolicy: providerDefaultPolicy } = useScaleMule();
|
|
8969
|
+
const { storage, photo, video, audio, mediaPolicy: providerDefaultPolicy } = useScaleMule();
|
|
8883
8970
|
const [uploading, setUploading] = useState(false);
|
|
8884
8971
|
const [error, setError] = useState(null);
|
|
8885
8972
|
const upload = useCallback(
|
|
@@ -8933,6 +9020,21 @@ function useMedia() {
|
|
|
8933
9020
|
is_public: false
|
|
8934
9021
|
};
|
|
8935
9022
|
}
|
|
9023
|
+
if (mimeType.startsWith("audio/") && !isPublic) {
|
|
9024
|
+
const r2 = await audio.uploadViaStorage(file, sharedOpts);
|
|
9025
|
+
if (r2.error || !r2.data) {
|
|
9026
|
+
throw r2.error ?? { code: "upload_error", message: "Upload failed", status: 0 };
|
|
9027
|
+
}
|
|
9028
|
+
return {
|
|
9029
|
+
file_id: r2.data.file_id,
|
|
9030
|
+
photo_id: null,
|
|
9031
|
+
original_view_url: r2.data.original_view_url,
|
|
9032
|
+
optimized_url_promise: Promise.resolve(null),
|
|
9033
|
+
hls_url_promise: Promise.resolve(null),
|
|
9034
|
+
mime_type: mimeType,
|
|
9035
|
+
is_public: false
|
|
9036
|
+
};
|
|
9037
|
+
}
|
|
8936
9038
|
if (mimeType.startsWith("image/") && isPublic) {
|
|
8937
9039
|
const r2 = await storage.upload(file, {
|
|
8938
9040
|
...sharedOpts,
|
|
@@ -8975,7 +9077,7 @@ function useMedia() {
|
|
|
8975
9077
|
setUploading(false);
|
|
8976
9078
|
}
|
|
8977
9079
|
},
|
|
8978
|
-
[storage, photo, video, providerDefaultPolicy]
|
|
9080
|
+
[storage, photo, video, audio, providerDefaultPolicy]
|
|
8979
9081
|
);
|
|
8980
9082
|
const cancelUpload = useCallback(
|
|
8981
9083
|
async (fileId) => {
|
|
@@ -8996,9 +9098,28 @@ function useMedia() {
|
|
|
8996
9098
|
);
|
|
8997
9099
|
return { upload, cancelUpload, error, uploading };
|
|
8998
9100
|
}
|
|
9101
|
+
function conversationChannel(kind, id) {
|
|
9102
|
+
switch (kind) {
|
|
9103
|
+
case "large_room":
|
|
9104
|
+
return `conversation:lr:${id}`;
|
|
9105
|
+
case "broadcast":
|
|
9106
|
+
return `conversation:bc:${id}`;
|
|
9107
|
+
case "support":
|
|
9108
|
+
return `conversation:support:${id}`;
|
|
9109
|
+
case "standard":
|
|
9110
|
+
default:
|
|
9111
|
+
return `conversation:${id}`;
|
|
9112
|
+
}
|
|
9113
|
+
}
|
|
8999
9114
|
function useFileStatus(options) {
|
|
9000
|
-
const { storage } = useScaleMule();
|
|
9001
|
-
const {
|
|
9115
|
+
const { storage, realtime } = useScaleMule();
|
|
9116
|
+
const {
|
|
9117
|
+
fileId,
|
|
9118
|
+
pollIntervalMs = null,
|
|
9119
|
+
disabled = false,
|
|
9120
|
+
conversationId = null,
|
|
9121
|
+
conversationKind = "standard"
|
|
9122
|
+
} = options;
|
|
9002
9123
|
const [status, setStatus] = useState(null);
|
|
9003
9124
|
const [loading, setLoading] = useState(false);
|
|
9004
9125
|
const [error, setError] = useState(null);
|
|
@@ -9037,6 +9158,20 @@ function useFileStatus(options) {
|
|
|
9037
9158
|
}, pollIntervalMs);
|
|
9038
9159
|
return () => clearInterval(id);
|
|
9039
9160
|
}, [pollIntervalMs, disabled, fileId, status?.scan.status, fetchStatus]);
|
|
9161
|
+
useEffect(() => {
|
|
9162
|
+
if (!conversationId || !fileId || disabled) return;
|
|
9163
|
+
const channel = conversationChannel(conversationKind, conversationId);
|
|
9164
|
+
const unsub = realtime.subscribe(channel, (data) => {
|
|
9165
|
+
if (typeof data !== "object" || data === null) return;
|
|
9166
|
+
const payload = data;
|
|
9167
|
+
const wrapped = "data" in payload && typeof payload.data === "object" && payload.data !== null ? payload.data : payload;
|
|
9168
|
+
const evt = typeof payload.event === "string" ? payload.event : void 0;
|
|
9169
|
+
if (evt && evt !== "file_status_changed") return;
|
|
9170
|
+
if (wrapped.file_id !== fileId) return;
|
|
9171
|
+
void fetchStatus();
|
|
9172
|
+
});
|
|
9173
|
+
return unsub;
|
|
9174
|
+
}, [realtime, conversationId, conversationKind, fileId, disabled, fetchStatus]);
|
|
9040
9175
|
const isReady = status?.scan.status === "clean";
|
|
9041
9176
|
return { status, loading, error, isReady, refresh: fetchStatus };
|
|
9042
9177
|
}
|
|
@@ -9051,11 +9186,18 @@ function ScaleMuleMedia(props) {
|
|
|
9051
9186
|
style,
|
|
9052
9187
|
alt,
|
|
9053
9188
|
pollIntervalMs = 2e3,
|
|
9189
|
+
conversationId = null,
|
|
9190
|
+
conversationKind,
|
|
9054
9191
|
renderPlaceholder,
|
|
9055
9192
|
renderBlocked,
|
|
9056
9193
|
renderOverride
|
|
9057
9194
|
} = props;
|
|
9058
|
-
const { status, isReady } = useFileStatus({
|
|
9195
|
+
const { status, isReady } = useFileStatus({
|
|
9196
|
+
fileId,
|
|
9197
|
+
pollIntervalMs,
|
|
9198
|
+
conversationId,
|
|
9199
|
+
conversationKind
|
|
9200
|
+
});
|
|
9059
9201
|
const isImage = mimeType.startsWith("image/");
|
|
9060
9202
|
const isVideo = mimeType.startsWith("video/");
|
|
9061
9203
|
const isAudio = mimeType.startsWith("audio/");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/nextjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@scalemule/money": "^0.0.1",
|
|
57
|
-
"@scalemule/sdk": "^0.0.
|
|
57
|
+
"@scalemule/sdk": "^0.0.43"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"next": ">=14.0.0",
|