@scalemule/nextjs 0.1.12 → 0.1.14

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/dist/index.d.mts CHANGED
@@ -324,6 +324,42 @@ interface UseContentOptions {
324
324
  */
325
325
  declare function useContent(options?: UseContentOptions): UseContentReturn;
326
326
 
327
+ interface AudioFile {
328
+ audio_id: string;
329
+ file_id: string;
330
+ filename: string;
331
+ mime_type: string;
332
+ size_bytes: number;
333
+ status: string;
334
+ codec: string | null;
335
+ bit_rate_kbps: number | null;
336
+ duration_ms: number | null;
337
+ created_at: string;
338
+ original_view_url: string | null;
339
+ transcoded_url: string | null;
340
+ waveform_peaks?: unknown;
341
+ }
342
+ interface UseAudioUploadOptions {
343
+ filename?: string;
344
+ metadata?: Record<string, unknown>;
345
+ onProgress?: (progress: number) => void;
346
+ signal?: AbortSignal;
347
+ }
348
+ interface AudioUploadResult {
349
+ file_id: string;
350
+ audio_id: string | null;
351
+ original_view_url: string | null;
352
+ transcoded_url_promise: Promise<string | null>;
353
+ }
354
+ interface UseAudioReturn {
355
+ upload: (file: File | Blob, opts?: UseAudioUploadOptions) => Promise<AudioUploadResult>;
356
+ list: () => Promise<AudioFile[]>;
357
+ remove: (fileId: string) => Promise<void>;
358
+ error: ApiError | null;
359
+ loading: boolean;
360
+ }
361
+ declare function useAudio(): UseAudioReturn;
362
+
327
363
  /**
328
364
  * Conversation kinds recognized by the chat realtime channel naming scheme.
329
365
  * Matches `broadcast_to_conversation` in `ms/scalemule-chat/src/realtime.rs`.
@@ -996,4 +1032,4 @@ declare function createSafeLogger(prefix: string): {
996
1032
  error: (message: string, data?: unknown) => void;
997
1033
  };
998
1034
 
999
- 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 };
1035
+ export { ApiError$1 as ApiError, type AudioFile, type AudioUploadResult, 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, type UseAudioReturn, type UseAudioUploadOptions, 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, useAudio, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
package/dist/index.d.ts CHANGED
@@ -324,6 +324,42 @@ interface UseContentOptions {
324
324
  */
325
325
  declare function useContent(options?: UseContentOptions): UseContentReturn;
326
326
 
327
+ interface AudioFile {
328
+ audio_id: string;
329
+ file_id: string;
330
+ filename: string;
331
+ mime_type: string;
332
+ size_bytes: number;
333
+ status: string;
334
+ codec: string | null;
335
+ bit_rate_kbps: number | null;
336
+ duration_ms: number | null;
337
+ created_at: string;
338
+ original_view_url: string | null;
339
+ transcoded_url: string | null;
340
+ waveform_peaks?: unknown;
341
+ }
342
+ interface UseAudioUploadOptions {
343
+ filename?: string;
344
+ metadata?: Record<string, unknown>;
345
+ onProgress?: (progress: number) => void;
346
+ signal?: AbortSignal;
347
+ }
348
+ interface AudioUploadResult {
349
+ file_id: string;
350
+ audio_id: string | null;
351
+ original_view_url: string | null;
352
+ transcoded_url_promise: Promise<string | null>;
353
+ }
354
+ interface UseAudioReturn {
355
+ upload: (file: File | Blob, opts?: UseAudioUploadOptions) => Promise<AudioUploadResult>;
356
+ list: () => Promise<AudioFile[]>;
357
+ remove: (fileId: string) => Promise<void>;
358
+ error: ApiError | null;
359
+ loading: boolean;
360
+ }
361
+ declare function useAudio(): UseAudioReturn;
362
+
327
363
  /**
328
364
  * Conversation kinds recognized by the chat realtime channel naming scheme.
329
365
  * Matches `broadcast_to_conversation` in `ms/scalemule-chat/src/realtime.rs`.
@@ -996,4 +1032,4 @@ declare function createSafeLogger(prefix: string): {
996
1032
  error: (message: string, data?: unknown) => void;
997
1033
  };
998
1034
 
999
- 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 };
1035
+ export { ApiError$1 as ApiError, type AudioFile, type AudioUploadResult, 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, type UseAudioReturn, type UseAudioUploadOptions, 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, useAudio, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
package/dist/index.js CHANGED
@@ -2242,14 +2242,25 @@ var StorageService = class extends ServiceModule {
2242
2242
  },
2243
2243
  requestOpts
2244
2244
  );
2245
- } else {
2246
- telemetry?.emit(sessionId, "upload.completed", {
2247
- file_id,
2248
- size_bytes: file.size,
2249
- duration_ms: Date.now() - directStart
2250
- });
2245
+ return { data: null, error: completeResult.error };
2251
2246
  }
2252
- return completeResult;
2247
+ telemetry?.emit(sessionId, "upload.completed", {
2248
+ file_id,
2249
+ size_bytes: file.size,
2250
+ duration_ms: Date.now() - directStart
2251
+ });
2252
+ const d = completeResult.data;
2253
+ return {
2254
+ data: {
2255
+ id: d.file_id,
2256
+ filename: d.filename,
2257
+ content_type: d.content_type,
2258
+ size_bytes: d.size_bytes,
2259
+ url: d.url,
2260
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2261
+ },
2262
+ error: null
2263
+ };
2253
2264
  }
2254
2265
  // --------------------------------------------------------------------------
2255
2266
  // Multipart Upload
@@ -4458,15 +4469,19 @@ var BillingService = class extends ServiceModule {
4458
4469
  return this.retiredSurface(`/v1/money/billing/payments/${id}/sync`);
4459
4470
  }
4460
4471
  // --------------------------------------------------------------------------
4461
- // Legacy methods (backward compat)
4472
+ // Removed aliases (Phase 6 — billing-v2 retirement)
4473
+ //
4474
+ // These were soft-deprecated wrappers in earlier SDK versions. They now
4475
+ // throw at runtime so any caller still using them gets an immediate signal.
4476
+ // Replacements: createSubscription → subscribe(); getInvoices → listInvoices().
4462
4477
  // --------------------------------------------------------------------------
4463
- /** @deprecated Use subscribe() instead */
4478
+ /** @removed Use subscribe() instead. */
4464
4479
  async createSubscription(data) {
4465
- return this.subscribe(data);
4480
+ throw new Error("BillingService.createSubscription was removed in Phase 6. Use subscribe() instead.");
4466
4481
  }
4467
- /** @deprecated Use listInvoices() instead */
4482
+ /** @removed Use listInvoices() instead. */
4468
4483
  async getInvoices(params) {
4469
- return this.listInvoices(params);
4484
+ throw new Error("BillingService.getInvoices was removed in Phase 6. Use listInvoices() instead.");
4470
4485
  }
4471
4486
  };
4472
4487
  var AnalyticsService = class extends ServiceModule {
@@ -9021,6 +9036,193 @@ function useContent(options = {}) {
9021
9036
  ]
9022
9037
  );
9023
9038
  }
9039
+ var AUDIO_POLL_INTERVAL_MS = 1e3;
9040
+ var AUDIO_POLL_MAX_ATTEMPTS = 30;
9041
+ function isAudioFile(file) {
9042
+ return file.content_type.startsWith("audio/");
9043
+ }
9044
+ function toStorageBackedAudioFile(file) {
9045
+ return {
9046
+ audio_id: file.id,
9047
+ file_id: file.id,
9048
+ filename: file.filename,
9049
+ mime_type: file.content_type,
9050
+ size_bytes: file.size_bytes,
9051
+ status: "pending_transcode",
9052
+ codec: null,
9053
+ bit_rate_kbps: null,
9054
+ duration_ms: null,
9055
+ created_at: file.created_at,
9056
+ original_view_url: file.url ?? null,
9057
+ transcoded_url: null
9058
+ };
9059
+ }
9060
+ function isNotFoundError(error) {
9061
+ if (!error || typeof error !== "object") {
9062
+ return false;
9063
+ }
9064
+ const candidate = error;
9065
+ return candidate.status === 404 || candidate.statusCode === 404 || candidate.response?.status === 404 || candidate.code === "not_found" || candidate.message?.toLowerCase().includes("not found") === true;
9066
+ }
9067
+ function isAbortError(error) {
9068
+ return error instanceof DOMException && error.name === "AbortError" || !!error && typeof error === "object" && "name" in error && error.name === "AbortError";
9069
+ }
9070
+ async function waitForPollInterval(signal) {
9071
+ if (signal?.aborted) {
9072
+ return false;
9073
+ }
9074
+ return new Promise((resolve) => {
9075
+ const timeoutId = window.setTimeout(() => {
9076
+ cleanup();
9077
+ resolve(true);
9078
+ }, AUDIO_POLL_INTERVAL_MS);
9079
+ const onAbort = () => {
9080
+ cleanup();
9081
+ resolve(false);
9082
+ };
9083
+ const cleanup = () => {
9084
+ window.clearTimeout(timeoutId);
9085
+ signal?.removeEventListener("abort", onAbort);
9086
+ };
9087
+ signal?.addEventListener("abort", onAbort, { once: true });
9088
+ });
9089
+ }
9090
+ function useAudio() {
9091
+ const { client, audio } = useScaleMule();
9092
+ const [loading, setLoading] = React.useState(false);
9093
+ const [error, setError] = React.useState(null);
9094
+ const pollTranscodedUrl = React.useCallback(
9095
+ async (audioId, signal) => {
9096
+ for (let attempt = 0; attempt < AUDIO_POLL_MAX_ATTEMPTS; attempt++) {
9097
+ if (signal?.aborted) {
9098
+ return null;
9099
+ }
9100
+ try {
9101
+ const details = await client.get(`/v1/audios/${audioId}`);
9102
+ if (details.status === "ready" && details.url) {
9103
+ return details.url;
9104
+ }
9105
+ if (details.status === "failed") {
9106
+ return null;
9107
+ }
9108
+ } catch (err) {
9109
+ if (isAbortError(err) || signal?.aborted) {
9110
+ return null;
9111
+ }
9112
+ if (!isNotFoundError(err)) {
9113
+ throw err;
9114
+ }
9115
+ }
9116
+ if (!await waitForPollInterval(signal)) {
9117
+ return null;
9118
+ }
9119
+ }
9120
+ return null;
9121
+ },
9122
+ [client]
9123
+ );
9124
+ const upload = React.useCallback(
9125
+ async (file, opts) => {
9126
+ setLoading(true);
9127
+ setError(null);
9128
+ try {
9129
+ const result = await audio.uploadViaStorage(file, {
9130
+ filename: opts?.filename,
9131
+ metadata: opts?.metadata,
9132
+ onProgress: opts?.onProgress,
9133
+ signal: opts?.signal
9134
+ });
9135
+ if (result.error || !result.data) {
9136
+ throw result.error ?? { code: "upload_error", message: "Audio upload failed" };
9137
+ }
9138
+ return {
9139
+ file_id: result.data.file_id,
9140
+ audio_id: result.data.audio_id,
9141
+ original_view_url: result.data.original_view_url,
9142
+ transcoded_url_promise: result.data.audio_id ? pollTranscodedUrl(result.data.audio_id, opts?.signal) : Promise.resolve(null)
9143
+ };
9144
+ } catch (err) {
9145
+ const apiError = err;
9146
+ setError(apiError);
9147
+ throw err;
9148
+ } finally {
9149
+ setLoading(false);
9150
+ }
9151
+ },
9152
+ [audio, pollTranscodedUrl]
9153
+ );
9154
+ const list = React.useCallback(async () => {
9155
+ setLoading(true);
9156
+ setError(null);
9157
+ try {
9158
+ const response = await client.get("/v1/storage/my-files");
9159
+ const audioFiles = response.files.filter(isAudioFile);
9160
+ const enriched = await Promise.all(
9161
+ audioFiles.map(async (file) => {
9162
+ try {
9163
+ const details = await client.get(`/v1/audios/${file.id}`);
9164
+ return {
9165
+ audio_id: details.id,
9166
+ file_id: file.id,
9167
+ filename: file.filename,
9168
+ mime_type: file.content_type,
9169
+ size_bytes: details.size_bytes ?? file.size_bytes,
9170
+ status: details.status,
9171
+ codec: details.codec,
9172
+ bit_rate_kbps: details.bit_rate_kbps ?? null,
9173
+ duration_ms: details.duration_ms ?? null,
9174
+ created_at: details.created_at,
9175
+ original_view_url: file.url ?? null,
9176
+ transcoded_url: details.url ?? null,
9177
+ waveform_peaks: details.waveform_peaks
9178
+ };
9179
+ } catch {
9180
+ return toStorageBackedAudioFile(file);
9181
+ }
9182
+ })
9183
+ );
9184
+ return enriched;
9185
+ } catch (err) {
9186
+ const apiError = err;
9187
+ setError(apiError);
9188
+ throw err;
9189
+ } finally {
9190
+ setLoading(false);
9191
+ }
9192
+ }, [client]);
9193
+ const remove = React.useCallback(
9194
+ async (fileId) => {
9195
+ setLoading(true);
9196
+ setError(null);
9197
+ try {
9198
+ const [audioDeleteResult, storageDeleteResult] = await Promise.allSettled([
9199
+ client.delete(`/v1/audios/${fileId}`),
9200
+ client.delete(`/v1/storage/files/${fileId}`)
9201
+ ]);
9202
+ if (storageDeleteResult.status === "rejected" && !isNotFoundError(storageDeleteResult.reason)) {
9203
+ throw storageDeleteResult.reason ?? new Error("Storage delete failed");
9204
+ }
9205
+ if (audioDeleteResult.status === "rejected" && !isNotFoundError(audioDeleteResult.reason)) {
9206
+ console.warn("Audio record delete failed after storage delete", audioDeleteResult.reason);
9207
+ }
9208
+ } catch (err) {
9209
+ const apiError = err;
9210
+ setError(apiError);
9211
+ throw err;
9212
+ } finally {
9213
+ setLoading(false);
9214
+ }
9215
+ },
9216
+ [client]
9217
+ );
9218
+ return {
9219
+ upload,
9220
+ list,
9221
+ remove,
9222
+ error,
9223
+ loading
9224
+ };
9225
+ }
9024
9226
  function useMedia() {
9025
9227
  const { storage, photo, video, audio, mediaPolicy: providerDefaultPolicy } = useScaleMule();
9026
9228
  const [uploading, setUploading] = React.useState(false);
@@ -11246,6 +11448,7 @@ exports.normalizePhone = normalizePhone;
11246
11448
  exports.phoneCountries = phoneCountries;
11247
11449
  exports.sanitizeForLog = sanitizeForLog;
11248
11450
  exports.useAnalytics = useAnalytics;
11451
+ exports.useAudio = useAudio;
11249
11452
  exports.useAuth = useAuth;
11250
11453
  exports.useBilling = useBilling;
11251
11454
  exports.useContent = useContent;
package/dist/index.mjs CHANGED
@@ -2222,14 +2222,25 @@ var StorageService = class extends ServiceModule {
2222
2222
  },
2223
2223
  requestOpts
2224
2224
  );
2225
- } else {
2226
- telemetry?.emit(sessionId, "upload.completed", {
2227
- file_id,
2228
- size_bytes: file.size,
2229
- duration_ms: Date.now() - directStart
2230
- });
2225
+ return { data: null, error: completeResult.error };
2231
2226
  }
2232
- return completeResult;
2227
+ telemetry?.emit(sessionId, "upload.completed", {
2228
+ file_id,
2229
+ size_bytes: file.size,
2230
+ duration_ms: Date.now() - directStart
2231
+ });
2232
+ const d = completeResult.data;
2233
+ return {
2234
+ data: {
2235
+ id: d.file_id,
2236
+ filename: d.filename,
2237
+ content_type: d.content_type,
2238
+ size_bytes: d.size_bytes,
2239
+ url: d.url,
2240
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2241
+ },
2242
+ error: null
2243
+ };
2233
2244
  }
2234
2245
  // --------------------------------------------------------------------------
2235
2246
  // Multipart Upload
@@ -4438,15 +4449,19 @@ var BillingService = class extends ServiceModule {
4438
4449
  return this.retiredSurface(`/v1/money/billing/payments/${id}/sync`);
4439
4450
  }
4440
4451
  // --------------------------------------------------------------------------
4441
- // Legacy methods (backward compat)
4452
+ // Removed aliases (Phase 6 — billing-v2 retirement)
4453
+ //
4454
+ // These were soft-deprecated wrappers in earlier SDK versions. They now
4455
+ // throw at runtime so any caller still using them gets an immediate signal.
4456
+ // Replacements: createSubscription → subscribe(); getInvoices → listInvoices().
4442
4457
  // --------------------------------------------------------------------------
4443
- /** @deprecated Use subscribe() instead */
4458
+ /** @removed Use subscribe() instead. */
4444
4459
  async createSubscription(data) {
4445
- return this.subscribe(data);
4460
+ throw new Error("BillingService.createSubscription was removed in Phase 6. Use subscribe() instead.");
4446
4461
  }
4447
- /** @deprecated Use listInvoices() instead */
4462
+ /** @removed Use listInvoices() instead. */
4448
4463
  async getInvoices(params) {
4449
- return this.listInvoices(params);
4464
+ throw new Error("BillingService.getInvoices was removed in Phase 6. Use listInvoices() instead.");
4450
4465
  }
4451
4466
  };
4452
4467
  var AnalyticsService = class extends ServiceModule {
@@ -9001,6 +9016,193 @@ function useContent(options = {}) {
9001
9016
  ]
9002
9017
  );
9003
9018
  }
9019
+ var AUDIO_POLL_INTERVAL_MS = 1e3;
9020
+ var AUDIO_POLL_MAX_ATTEMPTS = 30;
9021
+ function isAudioFile(file) {
9022
+ return file.content_type.startsWith("audio/");
9023
+ }
9024
+ function toStorageBackedAudioFile(file) {
9025
+ return {
9026
+ audio_id: file.id,
9027
+ file_id: file.id,
9028
+ filename: file.filename,
9029
+ mime_type: file.content_type,
9030
+ size_bytes: file.size_bytes,
9031
+ status: "pending_transcode",
9032
+ codec: null,
9033
+ bit_rate_kbps: null,
9034
+ duration_ms: null,
9035
+ created_at: file.created_at,
9036
+ original_view_url: file.url ?? null,
9037
+ transcoded_url: null
9038
+ };
9039
+ }
9040
+ function isNotFoundError(error) {
9041
+ if (!error || typeof error !== "object") {
9042
+ return false;
9043
+ }
9044
+ const candidate = error;
9045
+ return candidate.status === 404 || candidate.statusCode === 404 || candidate.response?.status === 404 || candidate.code === "not_found" || candidate.message?.toLowerCase().includes("not found") === true;
9046
+ }
9047
+ function isAbortError(error) {
9048
+ return error instanceof DOMException && error.name === "AbortError" || !!error && typeof error === "object" && "name" in error && error.name === "AbortError";
9049
+ }
9050
+ async function waitForPollInterval(signal) {
9051
+ if (signal?.aborted) {
9052
+ return false;
9053
+ }
9054
+ return new Promise((resolve) => {
9055
+ const timeoutId = window.setTimeout(() => {
9056
+ cleanup();
9057
+ resolve(true);
9058
+ }, AUDIO_POLL_INTERVAL_MS);
9059
+ const onAbort = () => {
9060
+ cleanup();
9061
+ resolve(false);
9062
+ };
9063
+ const cleanup = () => {
9064
+ window.clearTimeout(timeoutId);
9065
+ signal?.removeEventListener("abort", onAbort);
9066
+ };
9067
+ signal?.addEventListener("abort", onAbort, { once: true });
9068
+ });
9069
+ }
9070
+ function useAudio() {
9071
+ const { client, audio } = useScaleMule();
9072
+ const [loading, setLoading] = useState(false);
9073
+ const [error, setError] = useState(null);
9074
+ const pollTranscodedUrl = useCallback(
9075
+ async (audioId, signal) => {
9076
+ for (let attempt = 0; attempt < AUDIO_POLL_MAX_ATTEMPTS; attempt++) {
9077
+ if (signal?.aborted) {
9078
+ return null;
9079
+ }
9080
+ try {
9081
+ const details = await client.get(`/v1/audios/${audioId}`);
9082
+ if (details.status === "ready" && details.url) {
9083
+ return details.url;
9084
+ }
9085
+ if (details.status === "failed") {
9086
+ return null;
9087
+ }
9088
+ } catch (err) {
9089
+ if (isAbortError(err) || signal?.aborted) {
9090
+ return null;
9091
+ }
9092
+ if (!isNotFoundError(err)) {
9093
+ throw err;
9094
+ }
9095
+ }
9096
+ if (!await waitForPollInterval(signal)) {
9097
+ return null;
9098
+ }
9099
+ }
9100
+ return null;
9101
+ },
9102
+ [client]
9103
+ );
9104
+ const upload = useCallback(
9105
+ async (file, opts) => {
9106
+ setLoading(true);
9107
+ setError(null);
9108
+ try {
9109
+ const result = await audio.uploadViaStorage(file, {
9110
+ filename: opts?.filename,
9111
+ metadata: opts?.metadata,
9112
+ onProgress: opts?.onProgress,
9113
+ signal: opts?.signal
9114
+ });
9115
+ if (result.error || !result.data) {
9116
+ throw result.error ?? { code: "upload_error", message: "Audio upload failed" };
9117
+ }
9118
+ return {
9119
+ file_id: result.data.file_id,
9120
+ audio_id: result.data.audio_id,
9121
+ original_view_url: result.data.original_view_url,
9122
+ transcoded_url_promise: result.data.audio_id ? pollTranscodedUrl(result.data.audio_id, opts?.signal) : Promise.resolve(null)
9123
+ };
9124
+ } catch (err) {
9125
+ const apiError = err;
9126
+ setError(apiError);
9127
+ throw err;
9128
+ } finally {
9129
+ setLoading(false);
9130
+ }
9131
+ },
9132
+ [audio, pollTranscodedUrl]
9133
+ );
9134
+ const list = useCallback(async () => {
9135
+ setLoading(true);
9136
+ setError(null);
9137
+ try {
9138
+ const response = await client.get("/v1/storage/my-files");
9139
+ const audioFiles = response.files.filter(isAudioFile);
9140
+ const enriched = await Promise.all(
9141
+ audioFiles.map(async (file) => {
9142
+ try {
9143
+ const details = await client.get(`/v1/audios/${file.id}`);
9144
+ return {
9145
+ audio_id: details.id,
9146
+ file_id: file.id,
9147
+ filename: file.filename,
9148
+ mime_type: file.content_type,
9149
+ size_bytes: details.size_bytes ?? file.size_bytes,
9150
+ status: details.status,
9151
+ codec: details.codec,
9152
+ bit_rate_kbps: details.bit_rate_kbps ?? null,
9153
+ duration_ms: details.duration_ms ?? null,
9154
+ created_at: details.created_at,
9155
+ original_view_url: file.url ?? null,
9156
+ transcoded_url: details.url ?? null,
9157
+ waveform_peaks: details.waveform_peaks
9158
+ };
9159
+ } catch {
9160
+ return toStorageBackedAudioFile(file);
9161
+ }
9162
+ })
9163
+ );
9164
+ return enriched;
9165
+ } catch (err) {
9166
+ const apiError = err;
9167
+ setError(apiError);
9168
+ throw err;
9169
+ } finally {
9170
+ setLoading(false);
9171
+ }
9172
+ }, [client]);
9173
+ const remove = useCallback(
9174
+ async (fileId) => {
9175
+ setLoading(true);
9176
+ setError(null);
9177
+ try {
9178
+ const [audioDeleteResult, storageDeleteResult] = await Promise.allSettled([
9179
+ client.delete(`/v1/audios/${fileId}`),
9180
+ client.delete(`/v1/storage/files/${fileId}`)
9181
+ ]);
9182
+ if (storageDeleteResult.status === "rejected" && !isNotFoundError(storageDeleteResult.reason)) {
9183
+ throw storageDeleteResult.reason ?? new Error("Storage delete failed");
9184
+ }
9185
+ if (audioDeleteResult.status === "rejected" && !isNotFoundError(audioDeleteResult.reason)) {
9186
+ console.warn("Audio record delete failed after storage delete", audioDeleteResult.reason);
9187
+ }
9188
+ } catch (err) {
9189
+ const apiError = err;
9190
+ setError(apiError);
9191
+ throw err;
9192
+ } finally {
9193
+ setLoading(false);
9194
+ }
9195
+ },
9196
+ [client]
9197
+ );
9198
+ return {
9199
+ upload,
9200
+ list,
9201
+ remove,
9202
+ error,
9203
+ loading
9204
+ };
9205
+ }
9004
9206
  function useMedia() {
9005
9207
  const { storage, photo, video, audio, mediaPolicy: providerDefaultPolicy } = useScaleMule();
9006
9208
  const [uploading, setUploading] = useState(false);
@@ -11206,4 +11408,4 @@ function createSafeLogger(prefix) {
11206
11408
  };
11207
11409
  }
11208
11410
 
11209
- export { FeedbackWidget, ScaleMuleApiError, ScaleMuleClient2 as ScaleMuleClient, ScaleMuleMedia, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
11411
+ export { FeedbackWidget, ScaleMuleApiError, ScaleMuleClient2 as ScaleMuleClient, ScaleMuleMedia, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAudio, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useFileStatus, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
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.45 <0.1.0"
57
+ "@scalemule/sdk": "^0.0.47"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "next": ">=14.0.0",