@scalemule/nextjs 0.1.13 → 0.1.15

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.js CHANGED
@@ -4886,16 +4886,16 @@ var WebPushManager = class {
4886
4886
  userVisibleOnly: true,
4887
4887
  applicationServerKey: applicationServerKey.buffer
4888
4888
  });
4889
- const endpoint = pushSubscription.endpoint;
4889
+ const endpoint2 = pushSubscription.endpoint;
4890
4890
  const p256dh = arrayBufferToBase64url(pushSubscription.getKey("p256dh"));
4891
4891
  const auth = arrayBufferToBase64url(pushSubscription.getKey("auth"));
4892
4892
  const subscription = {
4893
- endpoint,
4893
+ endpoint: endpoint2,
4894
4894
  keys: { p256dh, auth }
4895
4895
  };
4896
4896
  const resolvedDeviceId = deviceId || this.state?.deviceId || generateDeviceId();
4897
4897
  const result = await this.fetcher.registerToken({
4898
- token: endpoint,
4898
+ token: endpoint2,
4899
4899
  platform: "web",
4900
4900
  device_id: resolvedDeviceId,
4901
4901
  subscription,
@@ -4907,12 +4907,12 @@ var WebPushManager = class {
4907
4907
  registration_source: this.registrationSource
4908
4908
  });
4909
4909
  this.state = {
4910
- endpoint,
4910
+ endpoint: endpoint2,
4911
4911
  tokenId: result.id,
4912
4912
  deviceId: resolvedDeviceId
4913
4913
  };
4914
4914
  this.persistState();
4915
- return { tokenId: result.id, endpoint };
4915
+ return { tokenId: result.id, endpoint: endpoint2 };
4916
4916
  }
4917
4917
  /** Unsubscribe from browser push and deregister token */
4918
4918
  async unsubscribe() {
@@ -6747,11 +6747,12 @@ var ScaleMule = class {
6747
6747
 
6748
6748
  // src/types/index.ts
6749
6749
  var ScaleMuleApiError = class extends Error {
6750
- constructor(error) {
6750
+ constructor(error, status) {
6751
6751
  super(error.message);
6752
6752
  this.name = "ScaleMuleApiError";
6753
6753
  this.code = error.code;
6754
6754
  this.field = error.field;
6755
+ this.status = status;
6755
6756
  }
6756
6757
  };
6757
6758
 
@@ -7522,6 +7523,45 @@ var ScaleMuleClient2 = class {
7522
7523
  function createClient(config) {
7523
7524
  return new ScaleMuleClient2(config);
7524
7525
  }
7526
+
7527
+ // src/sdk-telemetry.ts
7528
+ var endpoint;
7529
+ function setSdkTelemetryEndpoint(url) {
7530
+ endpoint = url;
7531
+ }
7532
+ function reportSdkError(payload) {
7533
+ if (!endpoint) return;
7534
+ if (typeof fetch === "undefined") return;
7535
+ const body = JSON.stringify({
7536
+ logs: [
7537
+ {
7538
+ message: `${payload.code}: ${payload.message}`,
7539
+ metadata: {
7540
+ name: payload.code,
7541
+ source: "scalemule-sdk",
7542
+ kind: "sdk",
7543
+ op: payload.op,
7544
+ status: payload.status ?? null,
7545
+ path: payload.path ?? null,
7546
+ field: payload.field ?? null,
7547
+ route: typeof window !== "undefined" ? window.location.pathname : null
7548
+ },
7549
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7550
+ }
7551
+ ]
7552
+ });
7553
+ try {
7554
+ void fetch(endpoint, {
7555
+ method: "POST",
7556
+ headers: { "Content-Type": "application/json" },
7557
+ body,
7558
+ keepalive: true,
7559
+ credentials: "same-origin"
7560
+ }).catch(() => {
7561
+ });
7562
+ } catch {
7563
+ }
7564
+ }
7525
7565
  var USER_CACHE_KEY = "scalemule_user";
7526
7566
  function getCachedUser() {
7527
7567
  if (typeof window === "undefined") return null;
@@ -7553,6 +7593,7 @@ function ScaleMuleProvider({
7553
7593
  storage,
7554
7594
  analyticsProxyUrl,
7555
7595
  authProxyUrl,
7596
+ telemetryEndpoint,
7556
7597
  publishableKey,
7557
7598
  enableAccountSwitcher,
7558
7599
  accountSwitcherPrivacy,
@@ -7566,6 +7607,10 @@ function ScaleMuleProvider({
7566
7607
  const [user, setUser] = React.useState(null);
7567
7608
  const [initializing, setInitializing] = React.useState(true);
7568
7609
  const [error, setError] = React.useState(null);
7610
+ React.useEffect(() => {
7611
+ setSdkTelemetryEndpoint(telemetryEndpoint);
7612
+ return () => setSdkTelemetryEndpoint(void 0);
7613
+ }, [telemetryEndpoint]);
7569
7614
  const resolvedGatewayUrl = gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com");
7570
7615
  const client = React.useMemo(
7571
7616
  () => createClient({
@@ -7659,8 +7704,12 @@ function ScaleMuleProvider({
7659
7704
  }
7660
7705
  }
7661
7706
  } catch {
7662
- if (mounted && debug) {
7663
- console.debug("[ScaleMule] Auth proxy session check failed");
7707
+ if (mounted) {
7708
+ setUser(null);
7709
+ setCachedUser(null);
7710
+ if (debug) {
7711
+ console.debug("[ScaleMule] Auth proxy session check failed; clearing cached user");
7712
+ }
7664
7713
  }
7665
7714
  } finally {
7666
7715
  client.resolveSessionPending();
@@ -7819,6 +7868,19 @@ async function proxyFetch(proxyUrl, path, options = {}) {
7819
7868
  // Include cookies for session management
7820
7869
  });
7821
7870
  const data = await response.json();
7871
+ if (data && data.error && !(data.error instanceof ScaleMuleApiError)) {
7872
+ data.error = new ScaleMuleApiError(data.error, response.status);
7873
+ }
7874
+ if (data && data.success === false && data.error) {
7875
+ reportSdkError({
7876
+ code: data.error.code,
7877
+ message: data.error.message,
7878
+ status: response.status,
7879
+ op: path,
7880
+ path: `${proxyUrl}/${path}`,
7881
+ field: data.error.field
7882
+ });
7883
+ }
7822
7884
  return data;
7823
7885
  }
7824
7886
  function useAuth() {
@@ -9036,6 +9098,193 @@ function useContent(options = {}) {
9036
9098
  ]
9037
9099
  );
9038
9100
  }
9101
+ var AUDIO_POLL_INTERVAL_MS = 1e3;
9102
+ var AUDIO_POLL_MAX_ATTEMPTS = 30;
9103
+ function isAudioFile(file) {
9104
+ return file.content_type.startsWith("audio/");
9105
+ }
9106
+ function toStorageBackedAudioFile(file) {
9107
+ return {
9108
+ audio_id: file.id,
9109
+ file_id: file.id,
9110
+ filename: file.filename,
9111
+ mime_type: file.content_type,
9112
+ size_bytes: file.size_bytes,
9113
+ status: "pending_transcode",
9114
+ codec: null,
9115
+ bit_rate_kbps: null,
9116
+ duration_ms: null,
9117
+ created_at: file.created_at,
9118
+ original_view_url: file.url ?? null,
9119
+ transcoded_url: null
9120
+ };
9121
+ }
9122
+ function isNotFoundError(error) {
9123
+ if (!error || typeof error !== "object") {
9124
+ return false;
9125
+ }
9126
+ const candidate = error;
9127
+ return candidate.status === 404 || candidate.statusCode === 404 || candidate.response?.status === 404 || candidate.code === "not_found" || candidate.message?.toLowerCase().includes("not found") === true;
9128
+ }
9129
+ function isAbortError(error) {
9130
+ return error instanceof DOMException && error.name === "AbortError" || !!error && typeof error === "object" && "name" in error && error.name === "AbortError";
9131
+ }
9132
+ async function waitForPollInterval(signal) {
9133
+ if (signal?.aborted) {
9134
+ return false;
9135
+ }
9136
+ return new Promise((resolve) => {
9137
+ const timeoutId = window.setTimeout(() => {
9138
+ cleanup();
9139
+ resolve(true);
9140
+ }, AUDIO_POLL_INTERVAL_MS);
9141
+ const onAbort = () => {
9142
+ cleanup();
9143
+ resolve(false);
9144
+ };
9145
+ const cleanup = () => {
9146
+ window.clearTimeout(timeoutId);
9147
+ signal?.removeEventListener("abort", onAbort);
9148
+ };
9149
+ signal?.addEventListener("abort", onAbort, { once: true });
9150
+ });
9151
+ }
9152
+ function useAudio() {
9153
+ const { client, audio } = useScaleMule();
9154
+ const [loading, setLoading] = React.useState(false);
9155
+ const [error, setError] = React.useState(null);
9156
+ const pollTranscodedUrl = React.useCallback(
9157
+ async (audioId, signal) => {
9158
+ for (let attempt = 0; attempt < AUDIO_POLL_MAX_ATTEMPTS; attempt++) {
9159
+ if (signal?.aborted) {
9160
+ return null;
9161
+ }
9162
+ try {
9163
+ const details = await client.get(`/v1/audios/${audioId}`);
9164
+ if (details.status === "ready" && details.url) {
9165
+ return details.url;
9166
+ }
9167
+ if (details.status === "failed") {
9168
+ return null;
9169
+ }
9170
+ } catch (err) {
9171
+ if (isAbortError(err) || signal?.aborted) {
9172
+ return null;
9173
+ }
9174
+ if (!isNotFoundError(err)) {
9175
+ throw err;
9176
+ }
9177
+ }
9178
+ if (!await waitForPollInterval(signal)) {
9179
+ return null;
9180
+ }
9181
+ }
9182
+ return null;
9183
+ },
9184
+ [client]
9185
+ );
9186
+ const upload = React.useCallback(
9187
+ async (file, opts) => {
9188
+ setLoading(true);
9189
+ setError(null);
9190
+ try {
9191
+ const result = await audio.uploadViaStorage(file, {
9192
+ filename: opts?.filename,
9193
+ metadata: opts?.metadata,
9194
+ onProgress: opts?.onProgress,
9195
+ signal: opts?.signal
9196
+ });
9197
+ if (result.error || !result.data) {
9198
+ throw result.error ?? { code: "upload_error", message: "Audio upload failed" };
9199
+ }
9200
+ return {
9201
+ file_id: result.data.file_id,
9202
+ audio_id: result.data.audio_id,
9203
+ original_view_url: result.data.original_view_url,
9204
+ transcoded_url_promise: result.data.audio_id ? pollTranscodedUrl(result.data.audio_id, opts?.signal) : Promise.resolve(null)
9205
+ };
9206
+ } catch (err) {
9207
+ const apiError = err;
9208
+ setError(apiError);
9209
+ throw err;
9210
+ } finally {
9211
+ setLoading(false);
9212
+ }
9213
+ },
9214
+ [audio, pollTranscodedUrl]
9215
+ );
9216
+ const list = React.useCallback(async () => {
9217
+ setLoading(true);
9218
+ setError(null);
9219
+ try {
9220
+ const response = await client.get("/v1/storage/my-files");
9221
+ const audioFiles = response.files.filter(isAudioFile);
9222
+ const enriched = await Promise.all(
9223
+ audioFiles.map(async (file) => {
9224
+ try {
9225
+ const details = await client.get(`/v1/audios/${file.id}`);
9226
+ return {
9227
+ audio_id: details.id,
9228
+ file_id: file.id,
9229
+ filename: file.filename,
9230
+ mime_type: file.content_type,
9231
+ size_bytes: details.size_bytes ?? file.size_bytes,
9232
+ status: details.status,
9233
+ codec: details.codec,
9234
+ bit_rate_kbps: details.bit_rate_kbps ?? null,
9235
+ duration_ms: details.duration_ms ?? null,
9236
+ created_at: details.created_at,
9237
+ original_view_url: file.url ?? null,
9238
+ transcoded_url: details.url ?? null,
9239
+ waveform_peaks: details.waveform_peaks
9240
+ };
9241
+ } catch {
9242
+ return toStorageBackedAudioFile(file);
9243
+ }
9244
+ })
9245
+ );
9246
+ return enriched;
9247
+ } catch (err) {
9248
+ const apiError = err;
9249
+ setError(apiError);
9250
+ throw err;
9251
+ } finally {
9252
+ setLoading(false);
9253
+ }
9254
+ }, [client]);
9255
+ const remove = React.useCallback(
9256
+ async (fileId) => {
9257
+ setLoading(true);
9258
+ setError(null);
9259
+ try {
9260
+ const [audioDeleteResult, storageDeleteResult] = await Promise.allSettled([
9261
+ client.delete(`/v1/audios/${fileId}`),
9262
+ client.delete(`/v1/storage/files/${fileId}`)
9263
+ ]);
9264
+ if (storageDeleteResult.status === "rejected" && !isNotFoundError(storageDeleteResult.reason)) {
9265
+ throw storageDeleteResult.reason ?? new Error("Storage delete failed");
9266
+ }
9267
+ if (audioDeleteResult.status === "rejected" && !isNotFoundError(audioDeleteResult.reason)) {
9268
+ console.warn("Audio record delete failed after storage delete", audioDeleteResult.reason);
9269
+ }
9270
+ } catch (err) {
9271
+ const apiError = err;
9272
+ setError(apiError);
9273
+ throw err;
9274
+ } finally {
9275
+ setLoading(false);
9276
+ }
9277
+ },
9278
+ [client]
9279
+ );
9280
+ return {
9281
+ upload,
9282
+ list,
9283
+ remove,
9284
+ error,
9285
+ loading
9286
+ };
9287
+ }
9039
9288
  function useMedia() {
9040
9289
  const { storage, photo, video, audio, mediaPolicy: providerDefaultPolicy } = useScaleMule();
9041
9290
  const [uploading, setUploading] = React.useState(false);
@@ -9954,8 +10203,8 @@ function useAnalytics(options = {}) {
9954
10203
  return { tracked: 1, session_id: sessionIdRef.current || void 0 };
9955
10204
  }
9956
10205
  if (publishableKey && gatewayUrl) {
9957
- const endpoint2 = useV2 ? "/v1/analytics/v2/events" : "/v1/analytics/events";
9958
- fetch(`${gatewayUrl}${endpoint2}`, {
10206
+ const endpoint3 = useV2 ? "/v1/analytics/v2/events" : "/v1/analytics/events";
10207
+ fetch(`${gatewayUrl}${endpoint3}`, {
9959
10208
  method: "POST",
9960
10209
  headers: {
9961
10210
  "Content-Type": "application/json",
@@ -9968,8 +10217,8 @@ function useAnalytics(options = {}) {
9968
10217
  });
9969
10218
  return { tracked: 1, session_id: sessionIdRef.current || void 0 };
9970
10219
  }
9971
- const endpoint = useV2 ? "/v1/analytics/v2/events" : "/v1/analytics/events";
9972
- return await client.post(endpoint, fullEvent);
10220
+ const endpoint2 = useV2 ? "/v1/analytics/v2/events" : "/v1/analytics/events";
10221
+ return await client.post(endpoint2, fullEvent);
9973
10222
  },
9974
10223
  // Note: sessionId removed - we use ref to keep this stable
9975
10224
  [client, buildFullEvent, useV2, analyticsProxyUrl, publishableKey, gatewayUrl]
@@ -10045,8 +10294,8 @@ function useAnalytics(options = {}) {
10045
10294
  return { tracked: events.length, session_id: sessionIdRef.current || void 0 };
10046
10295
  }
10047
10296
  if (publishableKey && gatewayUrl) {
10048
- const endpoint2 = useV2 ? "/v1/analytics/v2/events/batch" : "/v1/analytics/events/batch";
10049
- fetch(`${gatewayUrl}${endpoint2}`, {
10297
+ const endpoint3 = useV2 ? "/v1/analytics/v2/events/batch" : "/v1/analytics/events/batch";
10298
+ fetch(`${gatewayUrl}${endpoint3}`, {
10050
10299
  method: "POST",
10051
10300
  headers: {
10052
10301
  "Content-Type": "application/json",
@@ -10060,8 +10309,8 @@ function useAnalytics(options = {}) {
10060
10309
  setLoading(false);
10061
10310
  return { tracked: events.length, session_id: sessionIdRef.current || void 0 };
10062
10311
  }
10063
- const endpoint = useV2 ? "/v1/analytics/v2/events/batch" : "/v1/analytics/events/batch";
10064
- return await client.post(endpoint, {
10312
+ const endpoint2 = useV2 ? "/v1/analytics/v2/events/batch" : "/v1/analytics/events/batch";
10313
+ return await client.post(endpoint2, {
10065
10314
  events: fullEvents
10066
10315
  });
10067
10316
  } finally {
@@ -10197,10 +10446,10 @@ function useFeatureFlags(options = {}) {
10197
10446
  try {
10198
10447
  const currentKeys = keysRef.current;
10199
10448
  const payload = currentKeys && currentKeys.length > 0 ? { flag_keys: currentKeys, environment, context: contextRef.current } : { environment, context: contextRef.current };
10200
- const endpoint = currentKeys && currentKeys.length > 0 ? "/v1/flags/evaluate/batch" : "/v1/flags/evaluate/all";
10449
+ const endpoint2 = currentKeys && currentKeys.length > 0 ? "/v1/flags/evaluate/batch" : "/v1/flags/evaluate/all";
10201
10450
  let result;
10202
10451
  if (publishableKey && gatewayUrl) {
10203
- const response = await fetch(`${gatewayUrl}${endpoint}`, {
10452
+ const response = await fetch(`${gatewayUrl}${endpoint2}`, {
10204
10453
  method: "POST",
10205
10454
  headers: {
10206
10455
  "Content-Type": "application/json",
@@ -10214,7 +10463,7 @@ function useFeatureFlags(options = {}) {
10214
10463
  const json = await response.json();
10215
10464
  result = json.data || json || {};
10216
10465
  } else {
10217
- result = await client.post(endpoint, payload);
10466
+ result = await client.post(endpoint2, payload);
10218
10467
  }
10219
10468
  setFlags(result || {});
10220
10469
  setError(null);
@@ -11261,6 +11510,7 @@ exports.normalizePhone = normalizePhone;
11261
11510
  exports.phoneCountries = phoneCountries;
11262
11511
  exports.sanitizeForLog = sanitizeForLog;
11263
11512
  exports.useAnalytics = useAnalytics;
11513
+ exports.useAudio = useAudio;
11264
11514
  exports.useAuth = useAuth;
11265
11515
  exports.useBilling = useBilling;
11266
11516
  exports.useContent = useContent;